diff --git a/.github/package-filters/rs-packages-direct.yml b/.github/package-filters/rs-packages-direct.yml index 441c813702..3f5df68b57 100644 --- a/.github/package-filters/rs-packages-direct.yml +++ b/.github/package-filters/rs-packages-direct.yml @@ -118,6 +118,9 @@ platform-encryption: dash-platform-queries: - packages/dash-platform-queries/** +dash-platform-cxx: + - packages/rs-platform-cxx/** + dash-sdk: - packages/rs-sdk/** diff --git a/.github/package-filters/rs-packages-no-workflows.yml b/.github/package-filters/rs-packages-no-workflows.yml index 90835d0429..594d5b63ed 100644 --- a/.github/package-filters/rs-packages-no-workflows.yml +++ b/.github/package-filters/rs-packages-no-workflows.yml @@ -130,6 +130,15 @@ platform-encryption: &platform_encryption dash-platform-queries: &platform_queries - packages/dash-platform-queries/** +dash-platform-cxx: &platform_cxx + - packages/rs-platform-cxx/** + - *platform_queries + - *context_provider + - packages/rs-drive-proof-verifier/** + - *dpp + - *drive + - *dapi_grpc + dash-sdk: &sdk - packages/rs-drive-proof-verifier/** - packages/rs-sdk/** diff --git a/.github/package-filters/rs-packages.yml b/.github/package-filters/rs-packages.yml index 6fae2aa84a..7621be0839 100644 --- a/.github/package-filters/rs-packages.yml +++ b/.github/package-filters/rs-packages.yml @@ -155,6 +155,16 @@ dash-platform-queries: &platform_queries - .github/workflows/tests* - packages/dash-platform-queries/** +dash-platform-cxx: &platform_cxx + - .github/workflows/tests* + - packages/rs-platform-cxx/** + - *platform_queries + - *context_provider + - packages/rs-drive-proof-verifier/** + - *dpp + - *drive + - *dapi_grpc + dash-sdk: &sdk - .github/workflows/tests* - packages/rs-drive-proof-verifier/** diff --git a/.github/workflows/tests-rs-nightly-long-running.yml b/.github/workflows/tests-rs-nightly-long-running.yml index f65b87a37e..b7d3a45690 100644 --- a/.github/workflows/tests-rs-nightly-long-running.yml +++ b/.github/workflows/tests-rs-nightly-long-running.yml @@ -30,6 +30,7 @@ jobs: drive-abci, drive-proof-verifier, dash-platform-queries, + dash-platform-cxx, ] steps: - name: Check out repo diff --git a/.github/workflows/tests-rs-workspace.yml b/.github/workflows/tests-rs-workspace.yml index 1c159c2c70..136815a79c 100644 --- a/.github/workflows/tests-rs-workspace.yml +++ b/.github/workflows/tests-rs-workspace.yml @@ -193,6 +193,11 @@ jobs: cargo install cargo-machete 2>/dev/null || true cargo machete + - name: Check Platform CXX embedder + run: | + cargo test -p dash-platform-cxx --locked + CARGO_TARGET_DIR=target packages/rs-platform-cxx/test-cxx-link.sh + # The transport-free cuts are how embedders with their own networking # (Dash Core's platform GUI, explorers) consume verification: feature # unification hides regressions in whole-workspace builds, so check the @@ -203,13 +208,14 @@ jobs: cargo check -p dapi-grpc --no-default-features --features core,platform,client --locked cargo check -p drive-proof-verifier --locked cargo check -p dash-platform-queries --locked + cargo check -p dash-platform-cxx --locked # Native graphs: assert the networking transport stack stays out. # `tonic` itself is present (dapi-grpc's generated client types) but # without its transport feature — which is exactly what the absence # of hyper/rustls/tower proves. tokio is deliberately NOT asserted # absent: dash-context-provider depends on dash-async, which uses it # on native targets, and that edge predates the queries-crate split. - for native_package in drive-proof-verifier dash-platform-queries; do + for native_package in drive-proof-verifier dash-platform-queries dash-platform-cxx; do for banned in hyper rustls tower; do if cargo tree -p "$native_package" -e normal -i "$banned" 2>/dev/null | grep -q .; then echo "::error::$banned leaked into $native_package's dependency tree" diff --git a/Cargo.lock b/Cargo.lock index 92026cb5b5..090ad1fff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,7 +576,7 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -1211,6 +1211,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1229,7 +1240,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1549,6 +1560,68 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "cxx" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash 0.2.0", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap 2.14.0", + "proc-macro2", + "quote", + "scratch", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap 2.14.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c" +dependencies = [ + "indexmap 2.14.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "dapi-grpc" version = "4.2.0-dev.1" @@ -1693,6 +1766,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "dash-platform-cxx" +version = "4.2.0-dev.1" +dependencies = [ + "async-trait", + "ciborium", + "cxx", + "cxx-build", + "dapi-grpc", + "dash-context-provider", + "dash-platform-queries", + "dpp", + "drive-proof-verifier", + "futures", + "hex", + "platform-version", + "prost 0.14.4", + "serde", + "serde_json", +] + [[package]] name = "dash-platform-macros" version = "4.2.0-dev.1" @@ -1706,6 +1800,7 @@ dependencies = [ name = "dash-platform-queries" version = "4.2.0-dev.1" dependencies = [ + "ciborium", "dapi-grpc", "dash-context-provider", "dash-platform-macros", @@ -2493,7 +2588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2554,7 +2649,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3607,7 +3702,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -3858,7 +3953,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4287,6 +4382,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -5559,8 +5663,8 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "heck 0.4.1", - "itertools 0.10.5", + "heck 0.5.0", + "itertools 0.14.0", "log", "multimap", "petgraph", @@ -5581,7 +5685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5594,7 +5698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5730,7 +5834,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.5.10", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -5768,9 +5872,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.4", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6578,7 +6682,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6591,7 +6695,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6650,7 +6754,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6770,6 +6874,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + [[package]] name = "scrypt" version = "0.11.0" @@ -7447,6 +7557,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -7510,7 +7631,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7569,6 +7690,15 @@ dependencies = [ "zip 8.6.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "termtree" version = "0.5.1" @@ -8385,6 +8515,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -8959,7 +9095,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 23dcd8d051..4124ecf2c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ "packages/wasm-sdk", "packages/rs-unified-sdk-ffi", "packages/rs-unified-sdk-jni", + "packages/rs-platform-cxx", "packages/rs-scripts", ] diff --git a/Dockerfile b/Dockerfile index 846150203f..67be321482 100644 --- a/Dockerfile +++ b/Dockerfile @@ -416,6 +416,7 @@ COPY --parents \ packages/wasm-drive-verify \ packages/rs-dapi-client \ packages/dash-platform-queries \ + packages/rs-platform-cxx \ packages/rs-sdk \ packages/rs-sdk-ffi \ packages/rs-unified-sdk-ffi \ @@ -543,6 +544,7 @@ COPY --parents \ packages/wasm-drive-verify \ packages/rs-dapi-client \ packages/dash-platform-queries \ + packages/rs-platform-cxx \ packages/rs-sdk \ packages/rs-sdk-ffi \ packages/rs-unified-sdk-ffi \ @@ -693,6 +695,7 @@ COPY --parents \ packages/rs-drive \ packages/rs-drive-proof-verifier \ packages/dash-platform-queries \ + packages/rs-platform-cxx \ packages/rs-sdk \ packages/rs-sdk-trusted-context-provider \ # Common @@ -966,6 +969,7 @@ COPY --parents \ packages/wasm-drive-verify \ packages/rs-dapi-client \ packages/dash-platform-queries \ + packages/rs-platform-cxx \ packages/rs-sdk \ packages/rs-sdk-ffi \ packages/rs-unified-sdk-ffi \ diff --git a/packages/check-features/src/main.rs b/packages/check-features/src/main.rs index 8cba648541..a89521780c 100644 --- a/packages/check-features/src/main.rs +++ b/packages/check-features/src/main.rs @@ -11,6 +11,7 @@ fn main() { ("rs-drive-proof-verifier", vec![]), ("rs-platform-wallet", vec![]), ("dash-platform-queries", vec![]), + ("rs-platform-cxx", vec![]), ]; for (specific_crate, to_ignore) in crates { diff --git a/packages/dash-platform-queries/Cargo.toml b/packages/dash-platform-queries/Cargo.toml index f3d76e7c74..7c9870a739 100644 --- a/packages/dash-platform-queries/Cargo.toml +++ b/packages/dash-platform-queries/Cargo.toml @@ -17,6 +17,7 @@ mocks = [ ] [dependencies] +ciborium = { version = "0.2.2" } dapi-grpc = { path = "../dapi-grpc", default-features = false, features = [ "platform", "client", diff --git a/packages/dash-platform-queries/README.md b/packages/dash-platform-queries/README.md index 1587d9a58d..b4b71d1bab 100644 --- a/packages/dash-platform-queries/README.md +++ b/packages/dash-platform-queries/README.md @@ -39,17 +39,18 @@ If you want networking, retries, and a managed connection pool, use ## What's here -- `DocumentQuery` — rich document query builder with wire - encoding for both request versions. +- `DocumentQuery` — rich document query builder, wire encoding for both + request versions, and decoding **from** the wire request + (`DocumentQuery::try_from_request`) via decoders that mirror the server's + (`drive-abci`'s `v1/conversions.rs`) and are kept in lockstep with them. +- `verify_documents_response` — request-driven proof verification for document + queries, delegating to `drive-proof-verifier`'s `FromProof`. - Aggregate proof helpers (count/sum/average/ranked) shared with `dash-sdk`. -- DPNS username helpers — label normalization/validation and the - convertibility/contested checks shared with `dash-sdk`. -- `transition::validation` — structural validation for state transitions - ahead of signing. - -Wire-request decoding (`DocumentQuery::try_from_request`), request-driven -proof verification, and pure DPNS/DashPay document builders arrive in the -next slice of this series. +- Pure DPNS builders — `build_dpns_preorder_and_domain_documents`, label + normalization/validation — and pure DashPay contact-request document + assembly (`dashpay::build_contact_request_document`); crypto material is + supplied by the caller, keys never enter this crate. +- `transition::validation` helpers. ## Feature flags diff --git a/packages/dash-platform-queries/src/dashpay.rs b/packages/dash-platform-queries/src/dashpay.rs new file mode 100644 index 0000000000..37d9bbf5fd --- /dev/null +++ b/packages/dash-platform-queries/src/dashpay.rs @@ -0,0 +1,143 @@ +//! Transport-free DashPay contact request document assembly. +//! +//! The Sdk-bound DashPay surface (recipient fetching, ECDH, encryption, +//! broadcasting) lives in `dash-sdk`; this module is the pure DIP-15 +//! `contactRequest` document assembly it shares with embedders. All crypto +//! material arrives here as bytes — key derivation and encryption stay with +//! the caller. + +use crate::Error; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::DataContract; +use dpp::document::Document; +use dpp::platform_value::Value; +use dpp::prelude::Identifier; +use std::collections::BTreeMap; + +/// Already-derived crypto material and metadata for a DIP-15 +/// `contactRequest` document. +/// +/// Everything here is plain data: the ECDH/encryption that produced +/// `encrypted_public_key` and `encrypted_account_label`, and the randomness +/// that produced `entropy`, happen in the caller (`dash-sdk` or an +/// embedder). +#[derive(Debug, Clone)] +pub struct ContactRequestDocumentParams { + /// The sender's identity id (the document owner) + pub sender_id: Identifier, + /// The recipient's identity id (`toUserId`) + pub recipient_id: Identifier, + /// The sender's encryption key index used for ECDH + pub sender_key_index: u32, + /// The recipient's key index used for ECDH + pub recipient_key_index: u32, + /// Reference to the DashPay receiving account + pub account_reference: u32, + /// ECDH-encrypted extended public key: exactly 96 bytes + /// (16-byte IV + 80 bytes of encrypted DIP-15 compact xpub) + pub encrypted_public_key: Vec, + /// Optional encrypted account label: 48-80 bytes + /// (16-byte IV + 32-64 bytes of encrypted data) + pub encrypted_account_label: Option>, + /// Optional auto-accept proof (38-102 bytes) - not encrypted + pub auto_accept_proof: Option>, + /// The entropy that derives the document id; the same entropy must be + /// attached to the create transition, or platform consensus rejects it + /// with `InvalidDocumentTransitionIdError`. + pub entropy: [u8; 32], +} + +/// Validate the size of a DIP-15 `autoAcceptProof` (38-102 bytes). +pub fn validate_auto_accept_proof(proof: &[u8]) -> Result<(), Error> { + if proof.len() < 38 || proof.len() > 102 { + return Err(Error::InvalidInput(format!( + "autoAcceptProof must be 38-102 bytes, got {}", + proof.len() + ))); + } + Ok(()) +} + +/// Build the id and property map of a DIP-15 `contactRequest` document from +/// already-derived crypto material. +/// +/// This is the pure document-assembly half of `dash-sdk`'s +/// `create_contact_request`: the document id derives from +/// `params.entropy`, and the property map carries exactly the fields the +/// DashPay contract defines (`toUserId`, `encryptedPublicKey`, +/// `senderKeyIndex`, `recipientKeyIndex`, `accountReference`, plus the +/// optional `encryptedAccountLabel` and `autoAcceptProof`). +/// +/// Returns `(document_id, properties)`. +pub fn build_contact_request_document( + contract: &DataContract, + params: ContactRequestDocumentParams, +) -> Result<(Identifier, BTreeMap), Error> { + if let Some(ref proof) = params.auto_accept_proof { + validate_auto_accept_proof(proof)?; + } + + // Validate encrypted public key size (must be exactly 96 bytes: 16-byte IV + 80-byte encrypted data) + if params.encrypted_public_key.len() != 96 { + return Err(Error::InvalidInput(format!( + "Encrypted public key size mismatch: expected 96 bytes, got {}", + params.encrypted_public_key.len() + ))); + } + + // Validate encrypted label size (48-80 bytes: 16-byte IV + 32-64 byte encrypted data) + if let Some(ref label) = params.encrypted_account_label { + if label.len() < 48 || label.len() > 80 { + return Err(Error::InvalidInput(format!( + "Encrypted account label size out of range: expected 48-80 bytes, got {}", + label.len() + ))); + } + } + + let contact_request_document_type = + contract + .document_type_for_name("contactRequest") + .map_err(|_| { + Error::InvalidInput("DashPay contactRequest document type not found".to_string()) + })?; + + let document_id = Document::generate_document_id_v0( + &contract.id(), + ¶ms.sender_id, + contact_request_document_type.name(), + params.entropy.as_slice(), + ); + + let mut properties = BTreeMap::new(); + properties.insert( + "toUserId".to_string(), + Value::Identifier(params.recipient_id.to_buffer()), + ); + properties.insert( + "encryptedPublicKey".to_string(), + Value::Bytes(params.encrypted_public_key), + ); + properties.insert( + "senderKeyIndex".to_string(), + Value::U32(params.sender_key_index), + ); + properties.insert( + "recipientKeyIndex".to_string(), + Value::U32(params.recipient_key_index), + ); + properties.insert( + "accountReference".to_string(), + Value::U32(params.account_reference), + ); + + if let Some(label) = params.encrypted_account_label { + properties.insert("encryptedAccountLabel".to_string(), Value::Bytes(label)); + } + if let Some(proof) = params.auto_accept_proof { + properties.insert("autoAcceptProof".to_string(), Value::Bytes(proof)); + } + + Ok((document_id, properties)) +} diff --git a/packages/dash-platform-queries/src/documents/document_query.rs b/packages/dash-platform-queries/src/documents/document_query.rs index 113e6e1e47..5b9699a8dc 100644 --- a/packages/dash-platform-queries/src/documents/document_query.rs +++ b/packages/dash-platform-queries/src/documents/document_query.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use super::proto_conversions; use crate::error::Error; use dapi_grpc::platform::v0::get_documents_request::Version::{V0, V1}; use dapi_grpc::platform::v0::{ @@ -383,6 +384,516 @@ impl DocumentQuery { ) -> Result { GetDocumentsRequest::try_from_platform_versioned(self, platform_version) } + + /// Decode a wire-format [`GetDocumentsRequest`] back into a rich + /// [`DocumentQuery`] — the inverse of + /// [`Self::try_into_request_for_version`], and the piece that lets + /// an embedder verify a proved response given only the request + /// bytes it sent (see [`verify_documents_response`]). + /// + /// Both wire versions are handled, mirroring how the server + /// decodes each: + /// - **V0** carries `where` / `order_by` as CBOR-encoded arrays of + /// clause components; they are decoded exactly as + /// rs-drive-abci's `query_documents_v0` does (ciborium → + /// `Value::Array` → `WhereClause::from_components` / + /// `OrderClause::from_components`). V0 has no `select` / + /// `group_by` / `having` / `offset`; those default to the + /// documents-fetch shape. + /// - **V1** carries typed proto clauses; they are decoded through + /// the same [`proto_conversions`](super::proto_conversions) + /// functions the server's v1 handler runs, so client and server + /// cannot disagree on what the bytes mean. Multi-projection + /// `selects` (len > 1) is rejected — a `DocumentQuery` carries a + /// single projection, matching what the server evaluates. + /// `limit: Some(0)` is rejected, mirroring the server's uniform + /// `InvalidLimit` contract (`None` = server default → `0` + /// sentinel here; only positive caps are representable). + /// + /// The `prove` flag is intentionally ignored: `DocumentQuery` has + /// no prove field (its encoders always set `prove: true`, because + /// the `FromProof` decoders only handle proved responses). + /// + /// `contract` must be the data contract the request targets — the + /// request's `data_contract_id` is checked against `contract.id()` + /// and the named document type must exist on it. + /// + /// Scope caveat: this mirrors the server's *wire-shape* decoding + /// (shared clause decoders), not its full `validate_and_route` + /// business rules — e.g. SUM/AVG requiring a non-empty field, + /// GROUP BY being illegal with SELECT DOCUMENTS, or HAVING being + /// unimplemented are enforced server-side only. A request violating + /// those decodes here but can never yield a provable response from + /// a real server. That gap matters precisely for fabricated + /// request/response pairs, so the proof-verifying entry point + /// [`verify_documents_response`] closes it: it rejects every such + /// shape before delegating, rather than letting the lowering to + /// [`DriveDocumentQuery`] silently drop it. + pub fn try_from_request( + request: GetDocumentsRequest, + contract: Arc, + ) -> Result { + match request.version { + Some(V0(request_v0)) => Self::try_from_request_v0(request_v0, contract), + Some(V1(request_v1)) => Self::try_from_request_v1(request_v1, contract), + None => Err(Error::Protocol(ProtocolError::DecodingError( + "GetDocumentsRequest has no version set".to_string(), + ))), + } + } + + fn try_from_request_v0( + request: GetDocumentsRequestV0, + contract: Arc, + ) -> Result { + let GetDocumentsRequestV0 { + data_contract_id, + document_type, + r#where, + order_by, + limit, + // See `try_from_request`: DocumentQuery has no prove field. + prove: _, + start, + } = request; + + check_request_targets_contract(&contract, &data_contract_id, &document_type)?; + + let where_clauses = where_clauses_from_cbor(&r#where)?; + let order_by_clauses = order_clauses_from_cbor(&order_by)?; + + Ok(Self { + select: SelectProjection::documents(), + data_contract: contract, + document_type_name: document_type, + where_clauses, + group_by: Vec::new(), + having: Vec::new(), + order_by_clauses, + // V0's plain `uint32` uses the same `0` = "unset" sentinel + // as this struct — pass through. + limit, + offset: None, + start, + }) + } + + fn try_from_request_v1( + request: GetDocumentsRequestV1, + contract: Arc, + ) -> Result { + let GetDocumentsRequestV1 { + data_contract_id, + document_type, + where_clauses, + order_by, + limit, + start, + // See `try_from_request`: DocumentQuery has no prove field. + prove: _, + selects, + group_by, + having, + offset, + } = request; + + check_request_targets_contract(&contract, &data_contract_id, &document_type)?; + + let where_clauses = proto_conversions::where_clauses_from_proto(where_clauses)?; + let order_by_clauses = proto_conversions::order_clauses_from_proto(order_by)?; + let having = proto_conversions::having_clauses_from_proto(having)?; + + // Same shape the server's v1 handler accepts: 0 selects → + // default documents projection, 1 select → decode it, more → + // reject (a `DocumentQuery` carries a single projection; + // multi-projection is wire-only today and the server refuses + // it too). + if selects.len() > 1 { + return Err(Error::Protocol(ProtocolError::DecodingError(format!( + "multi-projection SELECT is not supported: a DocumentQuery carries a \ + single projection, got {} selects", + selects.len() + )))); + } + let select = selects + .into_iter() + .next() + .map(proto_conversions::select_from_proto) + .transpose()? + .unwrap_or_else(SelectProjection::documents); + + // Mirror the server's uniform v1 limit contract: `None` = use + // the server default (the `0` sentinel here), positive = + // explicit cap, `Some(0)` invalid (and unrepresentable — this + // struct's `0` means "unset"). + let limit = match limit { + None => 0, + Some(0) => { + return Err(Error::Protocol(ProtocolError::DecodingError( + "limit = 0 is not a valid wire value on the v1 `optional uint32` \ + field; omit `limit` (None) to use the server's default, or pass \ + a positive integer for an explicit cap" + .to_string(), + ))); + } + Some(n) => n, + }; + + // V1 ships its own `Start` enum with the same shape as V0's; + // this struct stores the V0 type (see `encode_v1` for the + // inverse translation). + let start = start.map(|s| match s { + V1Start::StartAfter(b) => Start::StartAfter(b), + V1Start::StartAt(b) => Start::StartAt(b), + }); + + Ok(Self { + select, + data_contract: contract, + document_type_name: document_type, + where_clauses, + group_by, + having, + order_by_clauses, + limit, + offset, + start, + }) + } +} + +/// Shared request-vs-contract consistency check for both wire +/// versions: the request must target the supplied contract, and the +/// named document type must exist on it. +fn check_request_targets_contract( + contract: &DataContract, + data_contract_id: &[u8], + document_type_name: &str, +) -> Result<(), Error> { + if data_contract_id != contract.id().as_slice() { + return Err(Error::Protocol(ProtocolError::DecodingError(format!( + "GetDocumentsRequest targets data contract {} but the supplied contract is {}", + hex::encode(data_contract_id), + contract.id() + )))); + } + contract + .document_type_for_name(document_type_name) + .map_err(ProtocolError::DataContractError)?; + Ok(()) +} + +/// Decode a V0 `where` field — CBOR bytes carrying an array of +/// `[field, operator, value]` component arrays — into structured +/// clauses. Byte-for-byte mirror of the decode the server's +/// `query_documents_v0` runs (empty bytes → no clauses; anything +/// else must be a CBOR array of arrays). +fn where_clauses_from_cbor(bytes: &[u8]) -> Result, Error> { + if bytes.is_empty() { + return Ok(Vec::new()); + } + let value: Value = ciborium::de::from_reader(bytes).map_err(|_| { + Error::Protocol(ProtocolError::DecodingError( + "unable to decode 'where' query from cbor".to_string(), + )) + })?; + match value { + Value::Null => Ok(Vec::new()), + Value::Array(clauses) => clauses + .iter() + .map(|wc| match wc { + Value::Array(components) => { + WhereClause::from_components(components).map_err(Error::Drive) + } + _ => Err(Error::Protocol(ProtocolError::DecodingError( + "where clause must be an array".to_string(), + ))), + }) + .collect(), + _ => Err(Error::Protocol(ProtocolError::DecodingError( + "where clause must be an array".to_string(), + ))), + } +} + +/// Decode a V0 `order_by` field — CBOR bytes carrying an array of +/// `[field, "asc"|"desc"]` component arrays — into structured +/// clauses. Mirror of the server-side decode, like +/// [`where_clauses_from_cbor`]. +fn order_clauses_from_cbor(bytes: &[u8]) -> Result, Error> { + if bytes.is_empty() { + return Ok(Vec::new()); + } + let value: Value = ciborium::de::from_reader(bytes).map_err(|_| { + Error::Protocol(ProtocolError::DecodingError( + "unable to decode 'order_by' query from cbor".to_string(), + )) + })?; + match value { + Value::Null => Ok(Vec::new()), + Value::Array(clauses) => clauses + .iter() + .map(|oc| match oc { + Value::Array(components) => { + OrderClause::from_components(components).map_err(|_| { + Error::Protocol(ProtocolError::DecodingError( + "invalid order_by clause components".to_string(), + )) + }) + } + _ => Err(Error::Protocol(ProtocolError::DecodingError( + "order_by clause must be an array".to_string(), + ))), + }) + .collect(), + _ => Err(Error::Protocol(ProtocolError::DecodingError( + "order_by must be an array".to_string(), + ))), + } +} + +/// Reject a request whose wire version (the `V0`/`V1` oneof arm, +/// i.e. feature version 0/1) falls outside the supplied platform +/// version's `drive_abci.query.document_query` bounds. +/// +/// This is the same `check_version` gate the server runs before it +/// decodes anything (`Platform::query_documents` in rs-drive-abci, +/// which answers an out-of-bounds wire version with +/// `QueryError::UnsupportedQueryVersion`). Without it, an untrusted +/// transport could pair a request wire-version the supplied platform +/// version's server refuses to serve with a valid proof produced for +/// the other wire shape, and verification would accept the pair. +/// +/// A missing `version` oneof is deliberately let through — the decode +/// that follows reports it with its established error message. +fn check_wire_version_is_served( + request: &GetDocumentsRequest, + platform_version: &PlatformVersion, +) -> Result<(), drive_proof_verifier::Error> { + let Some(version) = &request.version else { + return Ok(()); + }; + let feature_version: u16 = match version { + V0(_) => 0, + V1(_) => 1, + }; + let bounds = &platform_version.drive_abci.query.document_query; + if !bounds.check_version(feature_version) { + return Err(drive_proof_verifier::Error::RequestError { + error: format!( + "GetDocumentsRequest wire version V{feature_version} is outside the \ + document_query feature-version bounds {}..={} served at platform version \ + {}; the server answers such a request with UnsupportedQueryVersion, so no \ + proved response can belong to it", + bounds.min_version, bounds.max_version, platform_version.protocol_version + ), + }); + } + Ok(()) +} + +/// Reject the request shapes that can never have produced the proved +/// plain-document response being verified. +/// +/// Each rejection mirrors a gate an honest server runs before it would +/// ever build such a proof, and each covers a field the +/// `DocumentQuery` → [`DriveDocumentQuery`] lowering discards — which +/// is exactly the set an attacker could vary freely while replaying a +/// genuine proof. See [`verify_documents_response`] for the threat +/// model. +/// +/// This runs inside the [`FromProof`] impl itself — the +/// shared choke point — so every `DocumentQuery`-keyed verification +/// gets it: `dash-sdk`'s own document fetches talk to the same +/// untrusted nodes an embedder's transport does, and are protected by +/// the same gates as the wire-request entry point +/// [`verify_documents_response`]. +/// +/// Server counterparts, all in +/// `packages/rs-drive-abci/src/query/document_query/v1/mod.rs`: +/// `validate_and_route` rejects a non-empty HAVING for any +/// non-aggregate SELECT and a non-empty GROUP BY under SELECT +/// DOCUMENTS; `reject_offset_off_the_ranked_path` rejects any OFFSET +/// that did not route to the ranked executor (a documents fetch never +/// does). +fn reject_request_the_server_would_not_have_proved( + query: &DocumentQuery, +) -> Result<(), drive_proof_verifier::Error> { + let reject = |error: String| Err(drive_proof_verifier::Error::RequestError { error }); + + // This path verifies plain document fetches only. An aggregate + // projection (COUNT/SUM/AVG) is proved with a different proof shape; + // handing it to the Documents verifier would surface as an opaque + // low-level proof error, so reject it up front instead. + if query.select != drive::query::SelectProjection::documents() { + return reject(format!( + "only a plain SELECT DOCUMENTS fetch can be verified here; the request carries \ + the projection {:?} — aggregate projections are verified by the aggregate \ + proof helpers", + query.select + )); + } + if !query.having.is_empty() { + return reject(format!( + "request carries {} HAVING clause(s), which the server refuses for a \ + non-aggregate SELECT; no proved document response can belong to it", + query.having.len() + )); + } + if !query.group_by.is_empty() { + return reject(format!( + "request carries GROUP BY {:?}, which the server refuses under SELECT DOCUMENTS; \ + no proved document response can belong to it", + query.group_by + )); + } + if let Some(offset) = query.offset { + return reject(format!( + "request carries OFFSET {offset}, which the server accepts only on the ranked \ + surface, never for a document fetch; no proved document response can belong to it" + )); + } + Ok(()) +} + +/// Embedder entry point: verify a proved [`GetDocumentsResponse`] +/// directly against the wire request that produced it. +/// +/// This is the transport-free glue an embedder needs when it drives +/// its own transport: it holds the `GetDocumentsRequest` it sent and +/// the `GetDocumentsResponse` it got back, and this function does the +/// rest — decodes the request into a [`DocumentQuery`] (via +/// [`DocumentQuery::try_from_request`], whose decoders mirror the +/// server's request decode) and delegates to the existing +/// [`FromProof`] machinery, which resolves the +/// [`DriveDocumentQuery`] internally and cryptographically verifies +/// the proof against it. +/// +/// `contract` must be the data contract the request targets. If the +/// embedder's [`ContextProvider`] can resolve contracts, use +/// [`verify_documents_response_with_provider_contract`] instead and +/// skip the explicit parameter. +/// +/// # Binding the proof to the whole request +/// +/// GroveDB and Tenderdash proofs authenticate the state and the +/// resolved [`DriveDocumentQuery`] — not the request envelope. The +/// rich→drive lowering drops request fields that a documents query has +/// no place for (`group_by`, `having`, `offset`, `prove`), so +/// delegating without first checking them would let an untrusted +/// transport pair a request the real server would have *refused* with +/// a valid proof for the narrower query it lowers to, and this +/// function would accept it. Every such field is therefore rejected +/// before any proof machinery runs — `prove` here on the wire request, +/// and the query-shape fields inside the shared +/// [`FromProof`] impl (so `dash-sdk`'s own fetches run +/// the identical gates) — mirroring the server's own gates in +/// `rs-drive-abci`'s `validate_and_route` / +/// `reject_offset_off_the_ranked_path`. +/// +/// The same reasoning covers the request envelope itself: the wire +/// version (`V0`/`V1` oneof arm) is checked against +/// `platform_version.drive_abci.query.document_query`'s bounds before +/// anything is decoded — the server's `query_documents` dispatch +/// refuses an out-of-bounds wire version with +/// `UnsupportedQueryVersion`, so a proof can never belong to one — and +/// the query limit contract is enforced during the +/// `DriveDocumentQuery` lowering exactly as +/// `DriveDocumentQuery::from_typed_clauses` enforces it server-side: +/// an omitted limit resolves to the server default and a limit above +/// [`DEFAULT_QUERY_LIMIT`] is rejected. +pub fn verify_documents_response( + request: GetDocumentsRequest, + contract: Arc, + response: platform_proto::GetDocumentsResponse, + network: Network, + platform_version: &PlatformVersion, + provider: &dyn ContextProvider, +) -> Result<(Option, ResponseMetadata, Proof), drive_proof_verifier::Error> { + // First gate, mirroring the server's own dispatch order: a wire + // version the supplied platform version's server refuses to serve + // is rejected before any decoding or proof machinery. + check_wire_version_is_served(&request, platform_version)?; + // `prove` does not survive decoding (a `DocumentQuery` has no such + // field), so read it off the wire request before it is consumed. + // `prove: false` is not a server rejection — it makes the server + // return an unproved response, so a proved response cannot have + // come from one. + let prove = match &request.version { + Some(V0(v0)) => v0.prove, + Some(V1(v1)) => v1.prove, + // Missing version is reported by the decode below. + None => true, + }; + if !prove { + return Err(drive_proof_verifier::Error::RequestError { + error: "request carries prove=false, so an honest server would have answered it \ + with an unproved response; a proved response cannot belong to this request" + .to_string(), + }); + } + let query = DocumentQuery::try_from_request(request, contract).map_err(|e| { + drive_proof_verifier::Error::RequestError { + error: format!("failed to decode GetDocumentsRequest into a DocumentQuery: {e}"), + } + })?; + // The remaining request-shape gates + // (`reject_request_the_server_would_not_have_proved`) run inside the + // shared `FromProof` impl this delegates to. + >::maybe_from_proof_with_metadata( + query, + response, + network, + platform_version, + provider, + ) +} + +/// Variant of [`verify_documents_response`] that resolves the data +/// contract through the [`ContextProvider`] +/// ([`ContextProvider::get_data_contract`]) instead of taking it as a +/// parameter — for embedders whose provider already caches or fetches +/// contracts. +pub fn verify_documents_response_with_provider_contract( + request: GetDocumentsRequest, + response: platform_proto::GetDocumentsResponse, + network: Network, + platform_version: &PlatformVersion, + provider: &dyn ContextProvider, +) -> Result<(Option, ResponseMetadata, Proof), drive_proof_verifier::Error> { + // Same first gate as `verify_documents_response`, run here as well + // so an out-of-bounds wire version is rejected before the provider + // is asked for anything (the contract lookup below is already + // context-provider machinery). + check_wire_version_is_served(&request, platform_version)?; + let contract_id_bytes = match &request.version { + Some(V0(v0)) => v0.data_contract_id.as_slice(), + Some(V1(v1)) => v1.data_contract_id.as_slice(), + None => { + return Err(drive_proof_verifier::Error::RequestError { + error: "GetDocumentsRequest has no version set".to_string(), + }); + } + }; + let contract_id = Identifier::from_bytes(contract_id_bytes).map_err(|e| { + drive_proof_verifier::Error::RequestError { + error: format!("invalid data_contract_id in GetDocumentsRequest: {e}"), + } + })?; + let contract = provider + .get_data_contract(&contract_id, platform_version) + .map_err(drive_proof_verifier::Error::ContextProviderError)? + .ok_or_else(|| drive_proof_verifier::Error::RequestError { + error: format!("context provider has no data contract {contract_id}"), + })?; + verify_documents_response( + request, + contract, + response, + network, + platform_version, + provider, + ) } impl FromProof for Document { @@ -439,6 +950,12 @@ impl FromProof for drive_proof_verifier::types::Documents { Self: Sized + 'a, { let request: Self::Request = request.into(); + // Server-parity request gates run here, at the shared choke + // point, so every `DocumentQuery`-keyed verification — dash-sdk + // fetches and the wire-request entry points alike — rejects + // request shapes no honest server would have proved before any + // proof machinery runs. + reject_request_the_server_would_not_have_proved(&request)?; let drive_query: DriveDocumentQuery = (&request) .try_into() diff --git a/packages/dash-platform-queries/src/documents/mod.rs b/packages/dash-platform-queries/src/documents/mod.rs index 65cde6af08..eb9d793817 100644 --- a/packages/dash-platform-queries/src/documents/mod.rs +++ b/packages/dash-platform-queries/src/documents/mod.rs @@ -11,5 +11,11 @@ pub mod document_split_counts; pub mod document_split_sums; pub mod document_sum; pub(crate) mod having_proof_helpers; +/// Client-side wire-proto → drive-type decoders for `getDocuments`, +/// consumed by +/// [`document_query::DocumentQuery::try_from_request`]. They mirror +/// rs-drive-abci's server request decode and must be kept in +/// lockstep with it (see the module docs). +pub(crate) mod proto_conversions; pub(crate) mod ranked_proof_helpers; pub(crate) mod sum_proof_helpers; diff --git a/packages/dash-platform-queries/src/documents/proto_conversions.rs b/packages/dash-platform-queries/src/documents/proto_conversions.rs new file mode 100644 index 0000000000..2328310f26 --- /dev/null +++ b/packages/dash-platform-queries/src/documents/proto_conversions.rs @@ -0,0 +1,377 @@ +//! Wire-protobuf → drive type conversions for the `getDocuments` +//! query surface, used by +//! [`DocumentQuery::try_from_request`](super::document_query::DocumentQuery::try_from_request) +//! to rebuild the rich query from the wire request so a proved +//! response can be verified against exactly what was asked. +//! +//! This is a client-side **mirror** of the server's decode — +//! rs-drive-abci's `query/document_query/v1/conversions.rs` — and +//! must be kept in lockstep with it, exactly as the v0 path's CBOR +//! clause decoding mirrors `query_documents_v0`. The bytes the +//! server decodes and the bytes the verifier decodes must agree +//! clause-for-clause, or a proof could verify against a different +//! query than the server answered. Function bodies and error +//! message strings are copied verbatim from the server; any change +//! on either side must be replayed on the other. +//! +//! Conversion contract: +//! - Every fallible case maps to [`DecodeError::InvalidArgument`] +//! (malformed wire input, **not** future capability), except the +//! aggregate `ORDER BY` target which maps to +//! [`DecodeError::Unsupported`] (valid request shape, server +//! capability not yet wired). These mirror the server's +//! `QueryError::InvalidArgument` / `QuerySyntaxError::Unsupported` +//! respectively, preserving its error surface. +//! - Conversion is schema-agnostic. `DocumentFieldValue` variants +//! map 1:1 to `dpp::platform_value::Value` variants without +//! consulting the document type's schema. The schema-driven +//! coercion (`document_type.serialize_value_for_key`) runs +//! downstream as it does for the CBOR-shaped v0 path — a `text` +//! variant against an identifier field decodes via base58, a +//! `bytes_value` against the same field decodes as raw 32-byte +//! identifier, and so on. The wire layer just names the +//! primitive; the schema decides the indexed type. + +use dapi_grpc::platform::v0::get_documents_request::{ + document_field_value, + get_documents_request_v1::{select, Select as ProtoSelect}, + having_aggregate, having_clause, order_clause, DocumentFieldValue as ProtoDocumentFieldValue, + HavingAggregate as ProtoHavingAggregate, HavingClause as ProtoHavingClause, + OrderClause as ProtoOrderClause, WhereClause as ProtoWhereClause, + WhereOperator as ProtoWhereOperator, +}; +use dpp::platform_value::Value; +use drive::query::{ + HavingAggregate, HavingAggregateFunction, HavingClause, HavingOperator, HavingRightOperand, + OrderClause, SelectFunction, SelectProjection, WhereClause, WhereOperator, +}; + +/// Neutral decode error for the proto → drive conversions. +/// +/// Mirrors the two error shapes the server's decode produces +/// (`QueryError::InvalidArgument` and `QuerySyntaxError::Unsupported`) +/// without depending on them; the client-side `DocumentQuery` +/// decoding maps it onto the crate [`Error`](crate::error::Error). +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum DecodeError { + /// Malformed wire input — bad discriminant, missing oneof arm, + /// over-deep list nesting. No future protocol version would make + /// this input valid. + #[error("{0}")] + InvalidArgument(String), + /// Well-formed wire input naming a capability the decode target + /// cannot represent yet (e.g. `ORDER BY` on an aggregate key). + /// The wording signals future capability, not malformed request. + #[error("{0}")] + Unsupported(String), +} + +/// Map a wire-level [`ProtoWhereOperator`] discriminant onto +/// drive's [`WhereOperator`]. Unknown discriminants are wire-level +/// garbage (no future protocol value would map a malformed integer +/// to a valid behavior), so they surface as +/// [`DecodeError::InvalidArgument`]. +pub(crate) fn where_operator_from_proto(op: i32) -> Result { + let proto_op = ProtoWhereOperator::try_from(op).map_err(|_| { + DecodeError::InvalidArgument(format!( + "unknown WhereOperator discriminant: {} (valid values: 0..=10, see \ + `get_documents_request::WhereOperator`)", + op + )) + })?; + Ok(match proto_op { + ProtoWhereOperator::Equal => WhereOperator::Equal, + ProtoWhereOperator::GreaterThan => WhereOperator::GreaterThan, + ProtoWhereOperator::GreaterThanOrEquals => WhereOperator::GreaterThanOrEquals, + ProtoWhereOperator::LessThan => WhereOperator::LessThan, + ProtoWhereOperator::LessThanOrEquals => WhereOperator::LessThanOrEquals, + ProtoWhereOperator::Between => WhereOperator::Between, + ProtoWhereOperator::BetweenExcludeBounds => WhereOperator::BetweenExcludeBounds, + ProtoWhereOperator::BetweenExcludeLeft => WhereOperator::BetweenExcludeLeft, + ProtoWhereOperator::BetweenExcludeRight => WhereOperator::BetweenExcludeRight, + ProtoWhereOperator::In => WhereOperator::In, + ProtoWhereOperator::StartsWith => WhereOperator::StartsWith, + }) +} + +/// Map a wire [`ProtoDocumentFieldValue`] onto a +/// `dpp::platform_value::Value`. Schema-agnostic — variants map +/// 1:1 by primitive type and recurse for `list` up to a depth of +/// 1 (the only nesting level the query surface needs: `IN` / +/// `BETWEEN*` take a flat list of scalars). Anything deeper is +/// rejected as malformed wire input rather than recursed into, +/// so a hostile client can't blow the call stack with +/// `list(list(list(...)))` before schema validation. +/// +/// `None` (oneof unset on the wire) is rejected — a where-clause +/// operand is always concrete; empty where-clauses are expressed +/// by an empty `where_clauses` field at the request level, not by +/// sending an empty `DocumentFieldValue`. +pub(crate) fn value_from_proto(value: ProtoDocumentFieldValue) -> Result { + value_from_proto_at_depth(value, 0) +} + +/// Recursion-bounded form of [`value_from_proto`]. `depth = 0` is +/// the request-level operand; the only legal child shape is a +/// flat list (`depth = 1` for `IN` / `BETWEEN*` candidates), so a +/// `list` encountered at `depth >= 1` is wire-malformed. +fn value_from_proto_at_depth( + value: ProtoDocumentFieldValue, + depth: u8, +) -> Result { + let variant = value.variant.ok_or_else(|| { + DecodeError::InvalidArgument( + "DocumentFieldValue has no variant set; a where-clause operand must \ + be a concrete value" + .to_string(), + ) + })?; + Ok(match variant { + document_field_value::Variant::BoolValue(b) => Value::Bool(b), + document_field_value::Variant::Int64Value(i) => Value::I64(i), + document_field_value::Variant::Uint64Value(u) => Value::U64(u), + document_field_value::Variant::DoubleValue(f) => Value::Float(f), + document_field_value::Variant::Text(s) => Value::Text(s), + document_field_value::Variant::BytesValue(b) => Value::Bytes(b), + document_field_value::Variant::List(list) => { + if depth >= 1 { + return Err(DecodeError::InvalidArgument( + "nested DocumentFieldValue.list is not supported; the v1 \ + query surface accepts at most one level of nesting \ + (`IN` / `BETWEEN*` candidate lists of scalars)" + .to_string(), + )); + } + Value::Array( + list.values + .into_iter() + .map(|v| value_from_proto_at_depth(v, depth + 1)) + .collect::, _>>()?, + ) + } + // The bool payload is a placeholder — picking the + // `null_value` variant means "this operand is null" and + // the bool itself is ignored. See the proto-side comment + // on the field for the rationale. + document_field_value::Variant::NullValue(_) => Value::Null, + }) +} + +/// Map a wire [`ProtoWhereClause`] onto drive's structured +/// [`WhereClause`]. Errors surface as +/// [`DecodeError::InvalidArgument`] for both operator-discriminant +/// and value-shape failures. +pub(crate) fn where_clause_from_proto( + clause: ProtoWhereClause, +) -> Result { + let operator = where_operator_from_proto(clause.operator)?; + let value = clause.value.ok_or_else(|| { + DecodeError::InvalidArgument(format!( + "WhereClause on field '{}' has no value set; every clause must carry a \ + concrete `DocumentFieldValue`", + clause.field + )) + })?; + let value = value_from_proto(value)?; + Ok(WhereClause { + field: clause.field, + operator, + value, + }) +} + +/// Plural form of `where_clause_from_proto` for the request-level +/// `repeated WhereClause` field. Returns an error on the first +/// malformed clause. +pub fn where_clauses_from_proto( + clauses: Vec, +) -> Result, DecodeError> { + clauses.into_iter().map(where_clause_from_proto).collect() +} + +/// Map a wire [`ProtoOrderClause`] onto drive's [`OrderClause`]. +/// +/// The `target` oneof currently has two variants on the wire: +/// `field` (plain column name — evaluated today) and `aggregate` +/// (aggregate function applied to a field — wire-only, rejected +/// with [`DecodeError::Unsupported`]). Unset (`None`) is rejected +/// as malformed wire input. +pub(crate) fn order_clause_from_proto( + clause: ProtoOrderClause, +) -> Result { + let ascending = clause.ascending; + match clause.target { + Some(order_clause::Target::Field(field)) => Ok(OrderClause { field, ascending }), + Some(order_clause::Target::Aggregate(_)) => Err(DecodeError::Unsupported( + "ORDER BY on aggregate keys is not yet implemented".to_string(), + )), + None => Err(DecodeError::InvalidArgument( + "OrderClause has no target set; every clause must carry either a \ + `field` (plain column name) or an `aggregate` (aggregate-function \ + ordering target)" + .to_string(), + )), + } +} + +/// Plural form of `order_clause_from_proto` for the request-level +/// `repeated OrderClause` field. Returns the first error +/// encountered. +pub fn order_clauses_from_proto( + clauses: Vec, +) -> Result, DecodeError> { + clauses.into_iter().map(order_clause_from_proto).collect() +} + +/// Map a wire [`having_aggregate::Function`] discriminant onto +/// drive's [`HavingAggregateFunction`]. Unknown discriminants are +/// wire-level garbage (no future protocol value would map a +/// malformed integer to a valid behavior), so they surface as +/// [`DecodeError::InvalidArgument`]. +fn having_function_from_proto(function: i32) -> Result { + let proto = having_aggregate::Function::try_from(function).map_err(|_| { + DecodeError::InvalidArgument(format!( + "unknown HavingAggregate.Function discriminant: {} (valid values: 0..=2, see \ + `get_documents_request::having_aggregate::Function`)", + function + )) + })?; + Ok(match proto { + having_aggregate::Function::Count => HavingAggregateFunction::Count, + having_aggregate::Function::Sum => HavingAggregateFunction::Sum, + having_aggregate::Function::Avg => HavingAggregateFunction::Avg, + }) +} + +/// Map a wire [`having_clause::Operator`] discriminant onto +/// drive's [`HavingOperator`]. Same error contract as +/// [`having_function_from_proto`]. +fn having_operator_from_proto(operator: i32) -> Result { + let proto = having_clause::Operator::try_from(operator).map_err(|_| { + DecodeError::InvalidArgument(format!( + "unknown HavingClause.Operator discriminant: {} (valid values: 0..=10, see \ + `get_documents_request::having_clause::Operator`)", + operator + )) + })?; + Ok(match proto { + having_clause::Operator::Equal => HavingOperator::Equal, + having_clause::Operator::NotEqual => HavingOperator::NotEqual, + having_clause::Operator::GreaterThan => HavingOperator::GreaterThan, + having_clause::Operator::GreaterThanOrEquals => HavingOperator::GreaterThanOrEquals, + having_clause::Operator::LessThan => HavingOperator::LessThan, + having_clause::Operator::LessThanOrEquals => HavingOperator::LessThanOrEquals, + having_clause::Operator::Between => HavingOperator::Between, + having_clause::Operator::BetweenExcludeBounds => HavingOperator::BetweenExcludeBounds, + having_clause::Operator::BetweenExcludeLeft => HavingOperator::BetweenExcludeLeft, + having_clause::Operator::BetweenExcludeRight => HavingOperator::BetweenExcludeRight, + having_clause::Operator::In => HavingOperator::In, + }) +} + +/// Map a wire [`ProtoHavingAggregate`] onto drive's +/// [`HavingAggregate`]. The aggregate-function ↔ field +/// consistency check (`field` required for everything except +/// `Count`) runs inside the evaluator when HAVING execution +/// lands; the converter only enforces that the proto shape is +/// well-formed. +fn having_aggregate_from_proto( + aggregate: ProtoHavingAggregate, +) -> Result { + Ok(HavingAggregate { + function: having_function_from_proto(aggregate.function)?, + field: aggregate.field, + }) +} + +/// Map a wire [`ProtoHavingClause`] onto drive's structured +/// [`HavingClause`]. Errors surface as +/// [`DecodeError::InvalidArgument`] for any wire-level +/// malformation: unknown discriminant on the aggregate function or +/// operator; missing aggregate; missing right operand (oneof unset +/// on the wire); inner value-shape failures on the literal-value +/// branch. +/// +/// `HAVING` is a boolean per-group predicate and nothing else, so the +/// wire's `right` oneof has exactly one arm and this function has +/// exactly one thing to decode. Cross-group ranking is expressed with +/// SQL's own ordering surface — `ORDER BY DESC +/// LIMIT n [OFFSET m]` — which arrives as an `OrderClause` and never +/// reaches here. +pub(crate) fn having_clause_from_proto( + clause: ProtoHavingClause, +) -> Result { + let aggregate = clause.aggregate.ok_or_else(|| { + DecodeError::InvalidArgument( + "HavingClause has no aggregate set; every clause must carry an \ + aggregate function + field operand" + .to_string(), + ) + })?; + let aggregate = having_aggregate_from_proto(aggregate)?; + let operator = having_operator_from_proto(clause.operator)?; + let right = clause.right.ok_or_else(|| { + DecodeError::InvalidArgument( + "HavingClause has no right operand set; every clause must carry a \ + concrete `DocumentFieldValue` (`right.value`)" + .to_string(), + ) + })?; + let right = match right { + having_clause::Right::Value(v) => HavingRightOperand::Value(value_from_proto(v)?), + }; + Ok(HavingClause { + aggregate, + operator, + right, + }) +} + +/// Plural form of `having_clause_from_proto` for the request- +/// level `repeated HavingClause` field. Returns an error on the +/// first malformed clause. +pub fn having_clauses_from_proto( + clauses: Vec, +) -> Result, DecodeError> { + clauses.into_iter().map(having_clause_from_proto).collect() +} + +/// Map a wire [`select::Function`] discriminant onto drive's +/// [`SelectFunction`]. Unknown discriminants are wire-level +/// garbage (no future protocol value would map a malformed +/// integer to a valid behavior), so they surface as +/// [`DecodeError::InvalidArgument`]. +fn select_function_from_proto(function: i32) -> Result { + let proto = select::Function::try_from(function).map_err(|_| { + DecodeError::InvalidArgument(format!( + "unknown Select.Function discriminant: {} (valid values: 0..=5, see \ + `get_documents_request::get_documents_request_v1::select::Function`)", + function + )) + })?; + Ok(match proto { + select::Function::Documents => SelectFunction::Documents, + select::Function::Count => SelectFunction::Count, + select::Function::Sum => SelectFunction::Sum, + select::Function::Avg => SelectFunction::Avg, + select::Function::Min => SelectFunction::Min, + select::Function::Max => SelectFunction::Max, + }) +} + +/// Map a wire [`ProtoSelect`] onto drive's [`SelectProjection`]. +/// An unset `select` field on the request decodes as the proto- +/// default `Select { function: DOCUMENTS, field: "" }`, which +/// maps to [`SelectProjection::documents()`] — keeps callers that +/// don't set the field on the v0-style document-fetch path. +/// +/// Per-function field constraints (e.g. `DOCUMENTS` must have +/// empty `field`, `SUM`/`AVG` require non-empty) are checked at +/// routing time by the server's `validate_and_route`, not here, so +/// the converter only enforces well-formed proto. +pub fn select_from_proto(select: ProtoSelect) -> Result { + Ok(SelectProjection { + function: select_function_from_proto(select.function)?, + field: select.field, + }) +} diff --git a/packages/dash-platform-queries/src/dpns_usernames.rs b/packages/dash-platform-queries/src/dpns_usernames.rs index 3f452519b2..585ba8235b 100644 --- a/packages/dash-platform-queries/src/dpns_usernames.rs +++ b/packages/dash-platform-queries/src/dpns_usernames.rs @@ -1,8 +1,177 @@ //! Transport-free DPNS username helpers. //! //! The Sdk-bound DPNS surface (registration, availability checks, name -//! resolution) lives in `dash-sdk`; these free functions are pure string -//! validation/normalization shared with embedders. +//! resolution) lives in `dash-sdk`; the free functions here are the pure +//! pieces shared with embedders: string validation/normalization and the +//! preorder/domain document assembly used to register a name. + +use crate::Error; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::DataContract; +use dpp::document::{Document, DocumentV0}; +use dpp::platform_value::Value; +use dpp::prelude::Identifier; +use std::collections::BTreeMap; + +/// Hash a buffer twice using SHA256 (double SHA256) +fn hash_double(data: Vec) -> [u8; 32] { + use dpp::dashcore::hashes::{sha256d, Hash}; + // sha256d already does double SHA256 + let hash = sha256d::Hash::hash(&data); + hash.to_byte_array() +} + +/// Build the DPNS `preorder` and `domain` documents that register +/// `label`.dash for `identity_id`, exactly as platform consensus expects +/// them. +/// +/// This is the pure document-assembly half of `dash-sdk`'s +/// `register_dpns_name`: no networking, and no randomness — the caller +/// supplies the `entropy` that derives both document ids (the same entropy +/// must later be attached to both create transitions) and the preorder +/// `salt`, whose double-SHA256 over `salt ‖ ".dash"` +/// becomes the preorder's `saltedDomainHash`. +/// +/// The `label` must satisfy [`is_consensus_valid_label`]; the raw label is stored +/// in the domain document's `label` property while its +/// [homograph-safe](convert_to_homograph_safe_chars) form is stored in +/// `normalizedLabel`. +/// +/// # Salt secrecy and reveal order +/// +/// The preorder/domain split is DPNS's front-running protection: the +/// preorder commits to `saltedDomainHash` without revealing which name +/// is being registered, and only the later domain document discloses +/// the `label` and the `preorderSalt` that tie it to the commitment. +/// That protection holds only if the caller upholds what the networked +/// SDK (`dash-sdk`'s `register_dpns_name`) does automatically: +/// +/// - generate a **fresh 32-byte salt from a CSPRNG** for every +/// registration attempt (the SDK draws it from +/// `StdRng::from_entropy()`). A reused or predictable salt lets an +/// observer precompute `sha256d(salt ‖ ".dash")` for +/// candidate labels and identify — then front-run — the name from +/// the preorder alone; +/// - keep the salt, the label, and the assembled domain document +/// **private until the preorder create transition is confirmed** +/// (the SDK submits the preorder and waits for its response before +/// broadcasting the domain document). Revealing them earlier +/// discloses the name while it is still unclaimed, defeating the +/// commitment. +/// +/// Embedders driving their own transport inherit both obligations — +/// this builder takes `salt` as an argument precisely because it has +/// no randomness of its own and cannot enforce either one. +/// +/// Returns `(preorder_document, domain_document)`. +pub fn build_dpns_preorder_and_domain_documents( + contract: &DataContract, + identity_id: Identifier, + label: &str, + entropy: [u8; 32], + salt: [u8; 32], +) -> Result<(Document, Document), Error> { + if !is_consensus_valid_label(label) { + return Err(Error::InvalidInput(format!( + "Invalid DPNS label \"{label}\": must be 3-63 characters, alphanumeric and hyphens \ + only, starting and ending with an alphanumeric character" + ))); + } + + let preorder_document_type = contract + .document_type_for_name("preorder") + .map_err(|_| Error::InvalidInput("DPNS preorder document type not found".to_string()))?; + + let domain_document_type = contract + .document_type_for_name("domain") + .map_err(|_| Error::InvalidInput("DPNS domain document type not found".to_string()))?; + + let preorder_id = Document::generate_document_id_v0( + &contract.id(), + &identity_id, + preorder_document_type.name(), + entropy.as_slice(), + ); + let domain_id = Document::generate_document_id_v0( + &contract.id(), + &identity_id, + domain_document_type.name(), + entropy.as_slice(), + ); + + // Create salted domain hash for preorder + let normalized_label = convert_to_homograph_safe_chars(label); + let mut salted_domain_buffer: Vec = vec![]; + salted_domain_buffer.extend(salt); + salted_domain_buffer.extend((normalized_label.clone() + ".dash").as_bytes()); + let salted_domain_hash = hash_double(salted_domain_buffer); + + let preorder_document = Document::V0(DocumentV0 { + id: preorder_id, + owner_id: identity_id, + properties: BTreeMap::from([( + "saltedDomainHash".to_string(), + Value::Bytes32(salted_domain_hash), + )]), + revision: None, + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + }); + + let domain_document = Document::V0(DocumentV0 { + id: domain_id, + owner_id: identity_id, + properties: BTreeMap::from([ + ( + "parentDomainName".to_string(), + Value::Text("dash".to_string()), + ), + ( + "normalizedParentDomainName".to_string(), + Value::Text("dash".to_string()), + ), + ("label".to_string(), Value::Text(label.to_string())), + ("normalizedLabel".to_string(), Value::Text(normalized_label)), + ("preorderSalt".to_string(), Value::Bytes32(salt)), + ( + "records".to_string(), + Value::Map(vec![( + Value::Text("identity".to_string()), + Value::Identifier(identity_id.to_buffer()), + )]), + ), + ( + "subdomainRules".to_string(), + Value::Map(vec![( + Value::Text("allowSubdomains".to_string()), + Value::Bool(false), + )]), + ), + ]), + revision: None, + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + }); + + Ok((preorder_document, domain_document)) +} /// Convert a string to homograph-safe characters by replacing 'o', 'i', and 'l' /// with '0', '1', and '1' respectively to prevent homograph attacks @@ -18,15 +187,34 @@ pub fn convert_to_homograph_safe_chars(input: &str) -> String { .collect() } -/// Check if a username is valid according to DPNS rules -/// -/// A username is valid if: -/// - It's between 3 and 63 characters long -/// - It starts and ends with alphanumeric characters (a-zA-Z0-9) -/// - It contains only alphanumeric characters and hyphens -/// - It doesn't have consecutive hyphens (enforced by the pattern) +/// Check whether a label satisfies the DPNS contract's `label` schema +/// pattern — exactly what consensus enforces, nothing stricter. /// /// Pattern: `^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$` +/// (3-63 characters, alphanumeric and hyphens, alphanumeric at both ends; +/// consecutive hyphens ARE allowed by consensus). +pub fn is_consensus_valid_label(label: &str) -> bool { + if label.len() < 3 || label.len() > 63 { + return false; + } + let chars: Vec = label.chars().collect(); + if !chars[0].is_ascii_alphanumeric() || !chars[chars.len() - 1].is_ascii_alphanumeric() { + return false; + } + chars[1..chars.len() - 1] + .iter() + .all(|&ch| ch.is_ascii_alphanumeric() || ch == '-') +} + +/// Check if a username is valid according to this crate's recommended +/// client-side policy: the consensus pattern plus a stricter rejection of +/// consecutive hyphens. +/// +/// This is deliberately narrower than [`is_consensus_valid_label`] — a name +/// like `ab--cd` is consensus-valid but rejected here, matching the +/// pre-existing policy of the mobile SDK FFI and wasm-sdk gates. Callers +/// that must accept every consensus-valid label should use +/// [`is_consensus_valid_label`] instead. /// /// # Arguments /// @@ -36,38 +224,7 @@ pub fn convert_to_homograph_safe_chars(input: &str) -> String { /// /// Returns `true` if the username is valid, `false` otherwise pub fn is_valid_username(label: &str) -> bool { - // Check length - if label.len() < 3 || label.len() > 63 { - return false; - } - - let chars: Vec = label.chars().collect(); - - // Check first character (must be alphanumeric) - if !chars[0].is_ascii_alphanumeric() { - return false; - } - - // Check last character (must be alphanumeric) - if !chars[chars.len() - 1].is_ascii_alphanumeric() { - return false; - } - - // Check middle characters (can be alphanumeric or hyphen) - for &ch in &chars[1..chars.len() - 1] { - if !ch.is_ascii_alphanumeric() && ch != '-' { - return false; - } - } - - // Additional check: no consecutive hyphens (good practice) - for i in 0..chars.len() - 1 { - if chars[i] == '-' && chars[i + 1] == '-' { - return false; - } - } - - true + is_consensus_valid_label(label) && !label.contains("--") } /// Check if a username is contested (requires masternode voting) diff --git a/packages/dash-platform-queries/src/error.rs b/packages/dash-platform-queries/src/error.rs index 0d8727763c..f30573c3a7 100644 --- a/packages/dash-platform-queries/src/error.rs +++ b/packages/dash-platform-queries/src/error.rs @@ -15,6 +15,12 @@ pub enum Error { /// Query is not configured properly for the target platform version #[error("SDK misconfigured: {0}")] Config(String), + /// Input to a document builder failed validation (bad label, wrong + /// ciphertext length, unknown document type, ...). `dash-sdk` maps this + /// to its `Error::Generic`, preserving the messages these checks + /// produced before they moved here. + #[error("{0}")] + InvalidInput(String), /// Drive error #[error("Drive error: {0}")] Drive(#[from] drive::error::Error), @@ -23,6 +29,22 @@ pub enum Error { Protocol(#[from] ProtocolError), } +impl From for Error { + fn from(value: crate::documents::proto_conversions::DecodeError) -> Self { + use crate::documents::proto_conversions::DecodeError; + match value { + // Malformed wire bytes — a decoding failure, not a + // misconfiguration. + DecodeError::InvalidArgument(msg) => Self::Protocol(ProtocolError::DecodingError(msg)), + // Well-formed wire shape the decode target can't express + // yet — same classification the server gives it. + DecodeError::Unsupported(msg) => Self::Drive(drive::error::Error::Query( + drive::error::query::QuerySyntaxError::Unsupported(msg), + )), + } + } +} + impl From for Error { fn from(value: ConsensusError) -> Self { Self::Protocol(ProtocolError::ConsensusError(Box::new(value))) diff --git a/packages/dash-platform-queries/src/lib.rs b/packages/dash-platform-queries/src/lib.rs index 9da47cf643..29b71b8f43 100644 --- a/packages/dash-platform-queries/src/lib.rs +++ b/packages/dash-platform-queries/src/lib.rs @@ -13,6 +13,7 @@ #![allow(clippy::result_large_err)] pub mod block_info_from_metadata; +pub mod dashpay; pub mod documents; pub mod dpns_usernames; pub mod error; diff --git a/packages/dash-platform-queries/src/transition/mod.rs b/packages/dash-platform-queries/src/transition/mod.rs index 3a0f1376ad..097952677c 100644 --- a/packages/dash-platform-queries/src/transition/mod.rs +++ b/packages/dash-platform-queries/src/transition/mod.rs @@ -1,2 +1,3 @@ //! Transport-free state transition helpers. +pub mod put_document; pub mod validation; diff --git a/packages/dash-platform-queries/src/transition/put_document.rs b/packages/dash-platform-queries/src/transition/put_document.rs new file mode 100644 index 0000000000..3449bb5e67 --- /dev/null +++ b/packages/dash-platform-queries/src/transition/put_document.rs @@ -0,0 +1,176 @@ +//! Transport-free helpers for document create/replace transitions. +//! +//! `dash-sdk`'s `PutDocument` broadcast path calls these; embedders that +//! assemble their own transitions share the same preparation and +//! entropy/id consistency check. + +use crate::Error; +use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; +use dpp::data_contract::document_type::DocumentType; +use dpp::document::{Document, DocumentV0Getters}; +use dpp::prelude::Identifier; + +/// Returns a copy of `document` with its properties sanitized for the given +/// document type (e.g. integer arrays coerced back into byte arrays after a +/// WASM boundary crossing), leaving the caller's document untouched. +pub fn prepare_document_for_transition( + document: &Document, + document_type: &DocumentType, +) -> Document { + let mut document = document.clone(); + document_type + .as_ref() + .sanitize_document_properties(document.properties_mut()); + document +} + +/// Ensures a caller-supplied `entropy` derives the same document id already set +/// on a create document. +/// +/// A document-create state transition carries both the document id and the +/// entropy, and Drive recomputes the id from the entropy during +/// `advanced_structure` validation, rejecting the transition with +/// `InvalidDocumentTransitionIdError` when they disagree. Because the +/// broadcast path trusts the caller's id verbatim when entropy is supplied, +/// a two-phase caller whose id and entropy have drifted would only discover +/// the mismatch after paying (a bumped identity-contract nonce). This check +/// surfaces the mismatch locally before broadcasting. +pub fn ensure_entropy_matches_document_id( + contract_id: &Identifier, + owner_id: &Identifier, + document_type_name: &str, + entropy: &[u8; 32], + document_id: Identifier, +) -> Result<(), Error> { + let expected_id = Document::generate_document_id_v0( + contract_id, + owner_id, + document_type_name, + entropy.as_slice(), + ); + if expected_id != document_id { + return Err(Error::InvalidInput(format!( + "document id {document_id} does not match the id {expected_id} derived from the \ + supplied entropy; the entropy must be the one used to generate the document id" + ))); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use dpp::data_contract::config::DataContractConfig; + use dpp::document::{DocumentV0, INITIAL_REVISION}; + use dpp::platform_value::{platform_value, Value}; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + fn contract_id() -> Identifier { + Identifier::from([1u8; 32]) + } + + fn owner_id() -> Identifier { + Identifier::from([2u8; 32]) + } + + #[test] + fn matching_entropy_and_id_pass() { + let entropy = [7u8; 32]; + let id = Document::generate_document_id_v0( + &contract_id(), + &owner_id(), + "contactRequest", + entropy.as_slice(), + ); + + ensure_entropy_matches_document_id( + &contract_id(), + &owner_id(), + "contactRequest", + &entropy, + id, + ) + .expect("id derived from the supplied entropy must be accepted"); + } + + #[test] + fn mismatched_entropy_and_id_error_before_broadcast() { + // The id was derived from E1, but the caller passes E2 != E1 (mirroring + // the very drift consensus rejects with InvalidDocumentTransitionIdError). + let entropy_used = [1u8; 32]; + let id = Document::generate_document_id_v0( + &contract_id(), + &owner_id(), + "contactRequest", + entropy_used.as_slice(), + ); + + let different_entropy = [2u8; 32]; + let result = ensure_entropy_matches_document_id( + &contract_id(), + &owner_id(), + "contactRequest", + &different_entropy, + id, + ); + + assert!( + matches!(result, Err(Error::InvalidInput(_))), + "a document id derived from a different entropy must be rejected locally" + ); + } + + #[test] + fn should_normalize_wasm_uint8_array_property_without_mutating_caller_document() { + let platform_version = PlatformVersion::latest(); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create default data contract config"); + let document_type = DocumentType::try_from_schema( + contract_id(), + 1, + config.version(), + "preorder", + platform_value!({ + "type": "object", + "properties": { + "saltedDomainHash": { + "type": "array", + "byteArray": true, + "minItems": 32_u32, + "maxItems": 32_u32, + "position": 0 + } + }, + "required": ["saltedDomainHash"], + "additionalProperties": false, + }), + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ) + .expect("should create DPNS-like document type"); + let integer_array = Value::Array(vec![Value::U64(7); 32]); + let document = Document::V0(DocumentV0 { + id: Identifier::new([3; 32]), + owner_id: owner_id(), + properties: BTreeMap::from([("saltedDomainHash".to_string(), integer_array.clone())]), + revision: Some(INITIAL_REVISION), + ..Default::default() + }); + + let prepared = prepare_document_for_transition(&document, &document_type); + + assert_eq!( + prepared.properties().get("saltedDomainHash"), + Some(&Value::Bytes32([7; 32])) + ); + assert_eq!( + document.properties().get("saltedDomainHash"), + Some(&integer_array) + ); + } +} diff --git a/packages/rs-platform-cxx/Cargo.toml b/packages/rs-platform-cxx/Cargo.toml new file mode 100644 index 0000000000..00a9c0433a --- /dev/null +++ b/packages/rs-platform-cxx/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "dash-platform-cxx" +version.workspace = true +authors = ["Dash Core Group "] +edition = "2021" +rust-version.workspace = true +license = "MIT" +description = "Transport-free CXX bindings for embedding Dash Platform" + +[lib] +name = "dash_platform_cxx" +crate-type = ["staticlib", "rlib"] + +[dependencies] +cxx = "=1.0.198" +dapi-grpc = { path = "../dapi-grpc", default-features = false, features = [ + "platform", + "client", +] } +dash-context-provider = { path = "../rs-context-provider", default-features = false } +dash-platform-queries = { path = "../dash-platform-queries", default-features = false } +drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } +dpp = { path = "../rs-dpp", default-features = false, features = [ + "state-transitions", + "state-transition-signing", + "identity-serialization", + "identity-hashing", + "bls-signatures", + "dpns-contract", + "dashpay-contract", +] } +platform-version = { path = "../rs-platform-version" } +prost = "0.14" +hex = "0.4" +futures = "0.3" +async-trait = "0.1" + +[build-dependencies] +cxx-build = "=1.0.198" + +[features] +default = [] + +[dev-dependencies] +serde_json = "1" +serde = { version = "1", features = ["derive"] } +ciborium = "0.2" diff --git a/packages/rs-platform-cxx/README.md b/packages/rs-platform-cxx/README.md new file mode 100644 index 0000000000..2cbefd50b2 --- /dev/null +++ b/packages/rs-platform-cxx/README.md @@ -0,0 +1,31 @@ +# Dash Platform CXX + +`dash-platform-cxx` is the transport-free C++ embedding surface for Dash +Platform. It exposes proof verification, DPP decoding, and state-transition +construction while leaving transport, endpoint selection, quorum-state +synchronization, and private-key custody with the embedding application. + +The Rust implementation and CXX schema live in this package. Consumers build +the `standalone` manifest as one static library and include +`dash/platform/ffi.h` and `dash/platform/signer.h` from the generated CXX +include tree. The standalone manifest has its own lockfile so embedders can +vendor this package's dependency closure without vendoring unrelated Platform +workspace packages. + +Build and install the standalone archive with: + +```sh +CARGO_TARGET_DIR=/path/to/target \ + cargo build --manifest-path standalone/Cargo.toml --locked --release +CARGO_TARGET_DIR=/path/to/target ./install.sh /path/to/prefix +``` + +The installed interface consists of `include/dash/platform/ffi.h`, +`include/dash/platform/signer.h`, the generated CXX headers they require, and +`lib/libdash_platform_cxx.a`. The embedding application remains responsible +for the platform-specific system libraries required by a Rust static library. + +The normal dependency graph intentionally excludes `rs-dapi-client` and the +Hyper/Rustls/Tower transport stack. Generated protobuf client types, Tokio +utilities, and the context-provider abstraction remain available without a +native DAPI transport. diff --git a/packages/rs-platform-cxx/build.rs b/packages/rs-platform-cxx/build.rs new file mode 100644 index 0000000000..51b25e14e7 --- /dev/null +++ b/packages/rs-platform-cxx/build.rs @@ -0,0 +1,9 @@ +fn main() { + cxx_build::CFG.include_prefix = "dash/platform"; + cxx_build::bridge("src/lib.rs") + .std("c++20") + .compile("dash-platform-cxx-bridge"); + + println!("cargo:rerun-if-changed=src/"); + println!("cargo:rerun-if-changed=signer.h"); +} diff --git a/packages/rs-platform-cxx/ffi.h b/packages/rs-platform-cxx/ffi.h new file mode 100644 index 0000000000..2763b3fb8e --- /dev/null +++ b/packages/rs-platform-cxx/ffi.h @@ -0,0 +1,6 @@ +#ifndef DASH_PLATFORM_CXX_FFI_H +#define DASH_PLATFORM_CXX_FFI_H + +#include + +#endif // DASH_PLATFORM_CXX_FFI_H diff --git a/packages/rs-platform-cxx/install.sh b/packages/rs-platform-cxx/install.sh new file mode 100755 index 0000000000..237e2c40dc --- /dev/null +++ b/packages/rs-platform-cxx/install.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ "$#" -ne 1 ]]; then + echo "usage: $0 PREFIX" >&2 + exit 1 +fi + +package_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +workspace_dir="$(cd "${package_dir}/../.." && pwd)" +target_dir="${CARGO_TARGET_DIR:-${workspace_dir}/target/platform-cxx-standalone}" +profile="${CARGO_PROFILE:-release}" +artifact_dir="${target_dir}" +if [[ -n "${CARGO_BUILD_TARGET:-}" ]]; then + artifact_dir="${artifact_dir}/${CARGO_BUILD_TARGET}" +fi +artifact_dir="${artifact_dir}/${profile}" +prefix="$1" + +archive="${artifact_dir}/libdash_platform_cxx_bundle.a" +if [[ ! -f "${archive}" ]]; then + echo "missing standalone archive: ${archive}" >&2 + exit 1 +fi + +shopt -s nullglob +bridge_headers=("${artifact_dir}"/build/dash-platform-cxx-*/out/cxxbridge/include/dash/platform/src/lib.rs.h) +runtime_headers=("${artifact_dir}"/build/dash-platform-cxx-*/out/cxxbridge/include/rust/cxx.h) +if [[ "${#bridge_headers[@]}" -ne 1 || "${#runtime_headers[@]}" -ne 1 ]]; then + echo "expected exactly one generated CXX header set under ${artifact_dir}" >&2 + exit 1 +fi + +install -d \ + "${prefix}/include/dash/platform/src" \ + "${prefix}/include/rust" \ + "${prefix}/lib" +install -m 0644 "${package_dir}/ffi.h" "${prefix}/include/dash/platform/ffi.h" +install -m 0644 "${package_dir}/signer.h" "${prefix}/include/dash/platform/signer.h" +install -m 0644 "${bridge_headers[0]}" "${prefix}/include/dash/platform/src/lib.rs.h" +install -m 0644 "${runtime_headers[0]}" "${prefix}/include/rust/cxx.h" +install -m 0644 "${archive}" "${prefix}/lib/libdash_platform_cxx.a" diff --git a/packages/rs-platform-cxx/signer.h b/packages/rs-platform-cxx/signer.h new file mode 100644 index 0000000000..24daa0badd --- /dev/null +++ b/packages/rs-platform-cxx/signer.h @@ -0,0 +1,65 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_PLATFORM_CXX_SIGNER_H +#define DASH_PLATFORM_CXX_SIGNER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace platform_ffi { + +//! Signer handed by reference into the Rust state-transition builders. Rust +//! calls SignDigestForKey with the id of the +//! identity key being signed (or ASSET_LOCK_KEY_ID for the one-time +//! asset-lock key of an identity registration) and the 32-byte double-SHA256 +//! digest of the transition's signable bytes; the callback must return a +//! 65-byte compact recoverable ECDSA signature. Private keys never cross +//! the FFI boundary. +class WalletSigner +{ +public: + //! Reserved key id used for asset-lock proof signing (u32::MAX). + static constexpr uint32_t ASSET_LOCK_KEY_ID{0xffffffff}; + + using SignFn = std::function& digest, + std::vector& sig_out)>; + + explicit WalletSigner(SignFn sign_fn) : m_sign_fn(std::move(sign_fn)) {} + + bool SignDigestForKey(uint32_t key_id, rust::Slice digest, + rust::Vec& sig_out) const + { + if (!m_sign_fn || digest.size() != 32) return false; + std::array digest_array; + std::copy(digest.begin(), digest.end(), digest_array.begin()); + std::vector signature; + // This is called from Rust frames; a C++ exception must not unwind + // through them (unsupported by cxx), so a throwing signer reads as a + // signing refusal instead. + try { + if (!m_sign_fn(key_id, digest_array, signature)) return false; + } catch (...) { + return false; + } + sig_out.clear(); + sig_out.reserve(signature.size()); + std::copy(signature.begin(), signature.end(), std::back_inserter(sig_out)); + return true; + } + +private: + SignFn m_sign_fn; +}; + +} // namespace platform_ffi + +#endif // DASH_PLATFORM_CXX_SIGNER_H diff --git a/packages/rs-platform-cxx/src/decode.rs b/packages/rs-platform-cxx/src/decode.rs new file mode 100644 index 0000000000..e579ce98fa --- /dev/null +++ b/packages/rs-platform-cxx/src/decode.rs @@ -0,0 +1,168 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! DPP decoders for C++ identity and document value types, built on the real +//! rs-dpp deserializers. + +use std::collections::BTreeMap; + +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::document::{Document, DocumentV0Getters}; +use dpp::identity::accessors::IdentityGettersV0; +use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dpp::identity::{Identity, IdentityPublicKey}; +use dpp::platform_value::Value; +use dpp::serialization::PlatformDeserializable; +use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; +use platform_version::version::PlatformVersion; + +use crate::types::{ContactRequest, DpnsName, IdentityInfo, KeyInfo, Profile}; + +/// Flattens a dpp key into the FFI form. +pub(crate) fn key_info(key: &IdentityPublicKey) -> KeyInfo { + KeyInfo { + id: key.id(), + purpose: key.purpose() as u8, + security_level: key.security_level() as u8, + key_type: key.key_type() as u8, + read_only: key.read_only(), + data: key.data().to_vec(), + disabled_at: key.disabled_at(), + } +} + +/// Flattens a dpp identity into the FFI form. +pub(crate) fn identity_info(identity: &Identity) -> IdentityInfo { + IdentityInfo { + id: identity.id().to_buffer(), + balance: identity.balance(), + revision: identity.revision(), + keys: identity.public_keys().values().map(key_info).collect(), + } +} + +pub fn decode_identity(bytes: &[u8]) -> Result { + let identity = + Identity::deserialize_from_bytes(bytes).map_err(|e| format!("bad identity: {e}"))?; + Ok(identity_info(&identity)) +} + +pub fn decode_identity_public_key(bytes: &[u8]) -> Result { + let key = IdentityPublicKey::deserialize_from_bytes(bytes) + .map_err(|e| format!("bad identity public key: {e}"))?; + Ok(key_info(&key)) +} + +fn decode_document( + bytes: &[u8], + contract: SystemDataContract, + document_type_name: &str, +) -> Result { + use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0; + let version = PlatformVersion::latest(); + let contract = load_system_data_contract(contract, version) + .map_err(|e| format!("unable to load system data contract: {e}"))?; + let document_type = contract + .document_type_for_name(document_type_name) + .map_err(|e| format!("unknown document type {document_type_name}: {e}"))?; + Document::from_bytes(bytes, document_type, version) + .map_err(|e| format!("bad {document_type_name} document: {e}")) +} + +fn get_str(properties: &BTreeMap, name: &str) -> String { + match properties.get(name) { + Some(Value::Text(text)) => text.clone(), + _ => String::new(), + } +} + +fn get_bytes(properties: &BTreeMap, name: &str) -> Vec { + match properties.get(name) { + Some(Value::Bytes(bytes)) => bytes.clone(), + Some(Value::Bytes20(bytes)) => bytes.to_vec(), + Some(Value::Bytes32(bytes)) => bytes.to_vec(), + Some(Value::Bytes36(bytes)) => bytes.to_vec(), + Some(Value::Identifier(id)) => id.to_vec(), + _ => Vec::new(), + } +} + +fn get_u32(properties: &BTreeMap, name: &str) -> Result { + let Some(value) = properties.get(name) else { + return Ok(0); + }; + value + .clone() + .into_integer::() + .map_err(|e| format!("property {name} is not a u32: {e}")) +} + +fn get_identifier(value: Option<&Value>) -> Option<[u8; 32]> { + match value { + Some(Value::Identifier(id)) => Some(*id), + Some(Value::Bytes32(bytes)) => Some(*bytes), + Some(Value::Bytes(bytes)) => bytes.as_slice().try_into().ok(), + _ => None, + } +} + +pub fn decode_dpns_domain(doc_bytes: &[u8]) -> Result { + let document = decode_document(doc_bytes, SystemDataContract::DPNS, "domain")?; + let properties = document.properties(); + let identity = properties + .get("records") + .and_then(|records| match records { + Value::Map(map) => map + .iter() + .find(|(key, _)| matches!(key, Value::Text(text) if text == "identity")) + .map(|(_, value)| value), + _ => None, + }) + .and_then(|value| get_identifier(Some(value))) + .ok_or("DPNS domain document has no records.identity")?; + Ok(DpnsName { + label: get_str(properties, "label"), + normalized_label: get_str(properties, "normalizedLabel"), + parent_domain: get_str(properties, "normalizedParentDomainName"), + identity, + document_id: document.id().to_buffer(), + owner_id: document.owner_id().to_buffer(), + }) +} + +pub fn decode_dashpay_profile(doc_bytes: &[u8]) -> Result { + let document = decode_document(doc_bytes, SystemDataContract::Dashpay, "profile")?; + let properties = document.properties(); + Ok(Profile { + document_id: document.id().to_buffer(), + owner_id: document.owner_id().to_buffer(), + display_name: get_str(properties, "displayName"), + public_message: get_str(properties, "publicMessage"), + avatar_url: get_str(properties, "avatarUrl"), + avatar_hash: get_bytes(properties, "avatarHash"), + avatar_fingerprint: get_bytes(properties, "avatarFingerprint"), + created_at: document.created_at().unwrap_or(0), + updated_at: document.updated_at().unwrap_or(0), + revision: document.revision().unwrap_or(0), + }) +} + +pub fn decode_contact_request(doc_bytes: &[u8]) -> Result { + let document = decode_document(doc_bytes, SystemDataContract::Dashpay, "contactRequest")?; + let properties = document.properties(); + let to_user_id = get_identifier(properties.get("toUserId")) + .ok_or("contact request document has no toUserId")?; + Ok(ContactRequest { + document_id: document.id().to_buffer(), + owner_id: document.owner_id().to_buffer(), + to_user_id, + encrypted_public_key: get_bytes(properties, "encryptedPublicKey"), + sender_key_index: get_u32(properties, "senderKeyIndex")?, + recipient_key_index: get_u32(properties, "recipientKeyIndex")?, + account_reference: get_u32(properties, "accountReference")?, + encrypted_account_label: get_bytes(properties, "encryptedAccountLabel"), + core_height_created_at: document.created_at_core_block_height().unwrap_or(0), + created_at: document.created_at().unwrap_or(0), + }) +} diff --git a/packages/rs-platform-cxx/src/lib.rs b/packages/rs-platform-cxx/src/lib.rs new file mode 100644 index 0000000000..9f9f3f33cf --- /dev/null +++ b/packages/rs-platform-cxx/src/lib.rs @@ -0,0 +1,711 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Transport-free Dash Platform client internals exposed to C++ embedders. +//! +//! - `verify`: Drive/GroveDB proof verification over DAPI wire bytes; +//! - `decode`: DPP decoders for identities and DPNS/DashPay documents; +//! - `st`: state-transition construction with callback-based signing. +//! +//! The `#[cxx::bridge]` below exposes thin adapters over those modules. +//! Signing crosses the FFI as a digest callback (`WalletSigner`, +//! `dash/platform/signer.h`) so private keys never leave the embedder. + +pub mod decode; +pub mod provider; +pub mod st; +pub mod types; +pub mod verify; + +use types::{BuiltTransition, KeyInfo}; + +#[allow(clippy::too_many_arguments)] +#[cxx::bridge(namespace = "platform_ffi")] +mod ffi { + /// A byte vector, wrapped because cxx shared structs cannot hold + /// Vec>. + #[derive(Clone)] + struct FfiBytes { + data: Vec, + } + + /// One identity public key. `has_disabled_at == false` means the key is + /// not disabled. + #[derive(Clone)] + struct FfiIdentityKey { + id: u32, + purpose: u8, + security_level: u8, + key_type: u8, + read_only: bool, + data: Vec, + has_disabled_at: bool, + disabled_at: u64, + } + + /// One contender of a contested resource: identity id and its vote + /// tally (when requested/available). + struct FfiContender { + identity: Vec, + has_votes: bool, + votes: u32, + } + + /// Decoded identity. + struct FfiIdentity { + id: Vec, + balance: u64, + revision: u64, + keys: Vec, + } + + /// Decoded DPNS domain document. + struct FfiDpnsName { + label: String, + normalized_label: String, + parent_domain: String, + identity: Vec, + document_id: Vec, + owner_id: Vec, + } + + /// Decoded DashPay profile document. Empty vectors/strings and zero + /// timestamps mean the field is absent. + struct FfiProfile { + document_id: Vec, + owner_id: Vec, + display_name: String, + public_message: String, + avatar_url: String, + avatar_hash: Vec, + avatar_fingerprint: Vec, + created_at: u64, + updated_at: u64, + revision: u64, + } + + /// Decoded DashPay contactRequest document. + struct FfiContactRequest { + owner_id: Vec, + to_user_id: Vec, + encrypted_public_key: Vec, + sender_key_index: u32, + recipient_key_index: u32, + account_reference: u32, + encrypted_account_label: Vec, + core_height_created_at: u32, + created_at: u64, + document_id: Vec, + } + + /// A quorum BLS public key pushed from the node's LLMQ store. + /// `quorum_hash` (32 bytes) is in the byte order DAPI proofs carry it + /// (display order); `pubkey` is the 48-byte basic-scheme public key. + #[derive(Clone)] + struct FfiQuorumKey { + quorum_hash: Vec, + pubkey: Vec, + } + + /// Authenticated ResponseMetadata fields of a verified response (the + /// quorum signature covers them via the StateId sign bytes). + #[derive(Clone)] + struct FfiMeta { + height: u64, + core_chain_locked_height: u32, + time_ms: u64, + protocol_version: u32, + chain_id: String, + } + + /// Verified optional u64 (nonce); `present == false` means proven + /// absent. + struct FfiVerifiedU64 { + present: bool, + value: u64, + meta: FfiMeta, + } + + /// Verified optional identity; `present == false` means proven absent. + struct FfiVerifiedIdentity { + present: bool, + identity: FfiIdentity, + meta: FfiMeta, + } + + /// Verified document query result (serialized documents; empty means + /// proven no matches). + struct FfiVerifiedDocs { + documents: Vec, + meta: FfiMeta, + } + + /// Verified contested-resource vote state. + struct FfiVerifiedContested { + /// False when the contest was cryptographically proven absent. + contest_found: bool, + contenders: Vec, + has_abstain: bool, + abstain_votes: u32, + has_lock: bool, + lock_votes: u32, + /// True once the poll finished (awarded or locked). + finished: bool, + locked: bool, + has_winner: bool, + winner: Vec, + finished_at_time_ms: u64, + meta: FfiMeta, + } + + /// A public key to register with a new identity. + struct FfiNewIdentityKey { + id: u32, + purpose: u8, + security_level: u8, + /// Compressed secp256k1 public key (33 bytes). + pubkey: Vec, + } + + /// A built, signed state transition. `hash` is sha256(bytes), the wait + /// handle for waitForStateTransitionResult. + struct FfiBuiltTransition { + bytes: Vec, + hash: Vec, + } + + unsafe extern "C++" { + include!("dash/platform/signer.h"); + + /// Wallet-backed signer. `SignDigestForKey` signs a 32-byte digest + /// with the wallet key identified by `key_id` (`u32::MAX` selects + /// the one-time asset-lock key of an identity registration) and + /// returns a 65-byte compact recoverable ECDSA signature, or false + /// on failure. + type WalletSigner; + fn SignDigestForKey( + self: &WalletSigner, + key_id: u32, + digest: &[u8], + sig_out: &mut Vec, + ) -> bool; + } + + extern "Rust" { + // --- Node-local verification context ---------------------------- + /// Sets the network ("main"/"test"/"regtest"/"devnet") and the + /// Platform activation core height (0 = unknown; only consulted by + /// query paths that require it). + fn set_context(network_id: &str, platform_activation_height: u32) -> Result<()>; + /// Replaces the stored quorum keys of `quorum_type` with `keys`. + fn update_quorum_keys(quorum_type: u8, keys: Vec) -> Result<()>; + + // --- FromProof verification over (request, response) bytes ------ + fn verify_get_identity_nonce(request: &[u8], response: &[u8]) -> Result; + fn verify_get_identity_contract_nonce( + request: &[u8], + response: &[u8], + ) -> Result; + fn verify_get_identity(request: &[u8], response: &[u8]) -> Result; + fn verify_get_identity_by_pubkey_hash( + request: &[u8], + response: &[u8], + ) -> Result; + fn verify_get_documents(request: &[u8], response: &[u8]) -> Result; + fn verify_get_contested_vote_state( + request: &[u8], + response: &[u8], + ) -> Result; + + // --- DPP decoders ----------------------------------------------- + fn decode_identity(bytes: &[u8]) -> Result; + fn decode_identity_public_key(bytes: &[u8]) -> Result; + fn decode_dpns_domain(doc_bytes: &[u8]) -> Result; + fn decode_dashpay_profile(doc_bytes: &[u8]) -> Result; + fn decode_contact_request(doc_bytes: &[u8]) -> Result; + + // --- State transitions ------------------------------------------ + fn st_build_dpns_preorder( + identity_id: &[u8], + identity_contract_nonce: u64, + label: &str, + preorder_salt: &[u8], + signature_public_key_id: u32, + key: FfiIdentityKey, + signer: &WalletSigner, + ) -> Result; + fn st_build_dpns_domain( + identity_id: &[u8], + identity_contract_nonce: u64, + label: &str, + normalized_label: &str, + parent_domain: &str, + preorder_salt: &[u8], + signature_public_key_id: u32, + key: FfiIdentityKey, + signer: &WalletSigner, + ) -> Result; + fn st_build_profile( + identity_id: &[u8], + identity_contract_nonce: u64, + display_name: &str, + public_message: &str, + avatar_url: &str, + avatar_hash: &[u8], + avatar_fingerprint: &[u8], + revision: u64, + has_existing_doc_id: bool, + existing_document_id: &[u8], + entropy: &[u8], + signature_public_key_id: u32, + key: FfiIdentityKey, + signer: &WalletSigner, + ) -> Result; + fn st_build_contact_request( + identity_id: &[u8], + identity_contract_nonce: u64, + to_user_id: &[u8], + encrypted_public_key: &[u8], + sender_key_index: u32, + recipient_key_index: u32, + account_reference: u32, + encrypted_account_label: &[u8], + entropy: &[u8], + signature_public_key_id: u32, + key: FfiIdentityKey, + signer: &WalletSigner, + ) -> Result; + fn st_build_identity_create( + is_instant: bool, + transaction: &[u8], + instant_lock: &[u8], + output_index: u32, + core_chain_locked_height: u32, + out_point: &[u8], + keys: Vec, + signer: &WalletSigner, + ) -> Result; + } +} + +// --------------------------------------------------------------------------- +// Conversions between the plain-Rust core types and the flat FFI structs. +// --------------------------------------------------------------------------- + +fn ffi_key(key: &KeyInfo) -> ffi::FfiIdentityKey { + ffi::FfiIdentityKey { + id: key.id, + purpose: key.purpose, + security_level: key.security_level, + key_type: key.key_type, + read_only: key.read_only, + data: key.data.clone(), + has_disabled_at: key.disabled_at.is_some(), + disabled_at: key.disabled_at.unwrap_or(0), + } +} + +fn key_info(key: &ffi::FfiIdentityKey) -> KeyInfo { + KeyInfo { + id: key.id, + purpose: key.purpose, + security_level: key.security_level, + key_type: key.key_type, + read_only: key.read_only, + data: key.data.clone(), + disabled_at: key.has_disabled_at.then_some(key.disabled_at), + } +} + +fn ffi_built(built: BuiltTransition) -> ffi::FfiBuiltTransition { + ffi::FfiBuiltTransition { + bytes: built.bytes, + hash: built.hash.to_vec(), + } +} + +fn ffi_meta(meta: types::Meta) -> ffi::FfiMeta { + ffi::FfiMeta { + height: meta.height, + core_chain_locked_height: meta.core_chain_locked_height, + time_ms: meta.time_ms, + protocol_version: meta.protocol_version, + chain_id: meta.chain_id, + } +} + +fn ffi_verified_u64((value, meta): (Option, types::Meta)) -> ffi::FfiVerifiedU64 { + ffi::FfiVerifiedU64 { + present: value.is_some(), + value: value.unwrap_or(0), + meta: ffi_meta(meta), + } +} + +fn ffi_identity(identity: &types::IdentityInfo) -> ffi::FfiIdentity { + ffi::FfiIdentity { + id: identity.id.to_vec(), + balance: identity.balance, + revision: identity.revision, + keys: identity.keys.iter().map(ffi_key).collect(), + } +} + +fn ffi_verified_identity( + (identity, meta): (Option, types::Meta), +) -> ffi::FfiVerifiedIdentity { + ffi::FfiVerifiedIdentity { + present: identity.is_some(), + identity: identity + .as_ref() + .map(ffi_identity) + .unwrap_or_else(|| ffi::FfiIdentity { + id: Vec::new(), + balance: 0, + revision: 0, + keys: Vec::new(), + }), + meta: ffi_meta(meta), + } +} + +// --------------------------------------------------------------------------- +// Bridge implementations: verification context. +// --------------------------------------------------------------------------- + +fn set_context(network_id: &str, platform_activation_height: u32) -> Result<(), String> { + provider::set_context(network_id, platform_activation_height) +} + +fn update_quorum_keys(quorum_type: u8, keys: Vec) -> Result<(), String> { + let keys = keys + .into_iter() + .map(|key| { + Ok(provider::QuorumKey { + quorum_hash: types::id32(&key.quorum_hash, "quorum hash")?, + public_key: key.pubkey.as_slice().try_into().map_err(|_| { + format!( + "quorum public key must be 48 bytes, got {}", + key.pubkey.len() + ) + })?, + }) + }) + .collect::, String>>()?; + provider::update_quorum_keys(quorum_type, keys); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Bridge implementations: FromProof verification. +// --------------------------------------------------------------------------- + +fn verify_get_identity_nonce( + request: &[u8], + response: &[u8], +) -> Result { + verify::verify_get_identity_nonce(request, response).map(ffi_verified_u64) +} + +fn verify_get_identity_contract_nonce( + request: &[u8], + response: &[u8], +) -> Result { + verify::verify_get_identity_contract_nonce(request, response).map(ffi_verified_u64) +} + +fn verify_get_identity( + request: &[u8], + response: &[u8], +) -> Result { + verify::verify_get_identity(request, response).map(ffi_verified_identity) +} + +fn verify_get_identity_by_pubkey_hash( + request: &[u8], + response: &[u8], +) -> Result { + verify::verify_get_identity_by_pubkey_hash(request, response).map(ffi_verified_identity) +} + +fn verify_get_documents(request: &[u8], response: &[u8]) -> Result { + let (documents, meta) = verify::verify_get_documents(request, response)?; + Ok(ffi::FfiVerifiedDocs { + documents: documents + .into_iter() + .map(|data| ffi::FfiBytes { data }) + .collect(), + meta: ffi_meta(meta), + }) +} + +fn verify_get_contested_vote_state( + request: &[u8], + response: &[u8], +) -> Result { + let (state, meta) = verify::verify_get_contested_vote_state(request, response)?; + Ok(ffi::FfiVerifiedContested { + contest_found: state.contest_found, + contenders: state + .contenders + .iter() + .map(|(identity, votes)| ffi::FfiContender { + identity: identity.to_vec(), + has_votes: votes.is_some(), + votes: votes.unwrap_or(0), + }) + .collect(), + has_abstain: state.abstain_votes.is_some(), + abstain_votes: state.abstain_votes.unwrap_or(0), + has_lock: state.lock_votes.is_some(), + lock_votes: state.lock_votes.unwrap_or(0), + finished: state.finished, + locked: state.locked, + has_winner: state.winner.is_some(), + winner: state.winner.map(|id| id.to_vec()).unwrap_or_default(), + finished_at_time_ms: state.finished_at_time_ms, + meta: ffi_meta(meta), + }) +} + +fn decode_identity(bytes: &[u8]) -> Result { + decode::decode_identity(bytes).map(|identity| ffi_identity(&identity)) +} + +fn decode_identity_public_key(bytes: &[u8]) -> Result { + decode::decode_identity_public_key(bytes).map(|key| ffi_key(&key)) +} + +fn decode_dpns_domain(doc_bytes: &[u8]) -> Result { + let name = decode::decode_dpns_domain(doc_bytes)?; + Ok(ffi::FfiDpnsName { + label: name.label, + normalized_label: name.normalized_label, + parent_domain: name.parent_domain, + identity: name.identity.to_vec(), + document_id: name.document_id.to_vec(), + owner_id: name.owner_id.to_vec(), + }) +} + +fn decode_dashpay_profile(doc_bytes: &[u8]) -> Result { + let profile = decode::decode_dashpay_profile(doc_bytes)?; + Ok(ffi::FfiProfile { + document_id: profile.document_id.to_vec(), + owner_id: profile.owner_id.to_vec(), + display_name: profile.display_name, + public_message: profile.public_message, + avatar_url: profile.avatar_url, + avatar_hash: profile.avatar_hash, + avatar_fingerprint: profile.avatar_fingerprint, + created_at: profile.created_at, + updated_at: profile.updated_at, + revision: profile.revision, + }) +} + +fn decode_contact_request(doc_bytes: &[u8]) -> Result { + let request = decode::decode_contact_request(doc_bytes)?; + Ok(ffi::FfiContactRequest { + owner_id: request.owner_id.to_vec(), + to_user_id: request.to_user_id.to_vec(), + encrypted_public_key: request.encrypted_public_key, + sender_key_index: request.sender_key_index, + recipient_key_index: request.recipient_key_index, + account_reference: request.account_reference, + encrypted_account_label: request.encrypted_account_label, + core_height_created_at: request.core_height_created_at, + created_at: request.created_at, + document_id: request.document_id.to_vec(), + }) +} + +/// Shareable handle to the C++ signer. The bridge functions run the async +/// dpp builders to completion on the calling thread with a local executor, +/// so the signer is never actually accessed from another thread; the +/// `Send + Sync` assertion only satisfies dpp's `Signer: Send + Sync` +/// bound. +struct SignerHandle<'a>(&'a ffi::WalletSigner); +unsafe impl Send for SignerHandle<'_> {} +unsafe impl Sync for SignerHandle<'_> {} + +impl SignerHandle<'_> { + fn sign(&self, key_id: u32, digest: [u8; 32]) -> Option> { + let mut signature = Vec::new(); + self.0 + .SignDigestForKey(key_id, &digest, &mut signature) + .then_some(signature) + } +} + +fn check_key_id(signature_public_key_id: u32, key: &ffi::FfiIdentityKey) -> Result<(), String> { + if signature_public_key_id != key.id { + return Err(format!( + "signature public key id {signature_public_key_id} does not match key id {}", + key.id + )); + } + Ok(()) +} + +fn st_build_dpns_preorder( + identity_id: &[u8], + identity_contract_nonce: u64, + label: &str, + preorder_salt: &[u8], + signature_public_key_id: u32, + key: ffi::FfiIdentityKey, + signer: &ffi::WalletSigner, +) -> Result { + check_key_id(signature_public_key_id, &key)?; + let handle = SignerHandle(signer); + let sign_fn = |key_id: u32, digest: [u8; 32]| handle.sign(key_id, digest); + st::build_dpns_preorder( + identity_id, + identity_contract_nonce, + label, + preorder_salt, + &key_info(&key), + &sign_fn, + ) + .map(ffi_built) +} + +#[allow(clippy::too_many_arguments)] +fn st_build_dpns_domain( + identity_id: &[u8], + identity_contract_nonce: u64, + label: &str, + normalized_label: &str, + parent_domain: &str, + preorder_salt: &[u8], + signature_public_key_id: u32, + key: ffi::FfiIdentityKey, + signer: &ffi::WalletSigner, +) -> Result { + check_key_id(signature_public_key_id, &key)?; + let handle = SignerHandle(signer); + let sign_fn = |key_id: u32, digest: [u8; 32]| handle.sign(key_id, digest); + st::build_dpns_domain( + identity_id, + identity_contract_nonce, + label, + normalized_label, + parent_domain, + preorder_salt, + &key_info(&key), + &sign_fn, + ) + .map(ffi_built) +} + +#[allow(clippy::too_many_arguments)] +fn st_build_profile( + identity_id: &[u8], + identity_contract_nonce: u64, + display_name: &str, + public_message: &str, + avatar_url: &str, + avatar_hash: &[u8], + avatar_fingerprint: &[u8], + revision: u64, + has_existing_doc_id: bool, + existing_document_id: &[u8], + entropy: &[u8], + signature_public_key_id: u32, + key: ffi::FfiIdentityKey, + signer: &ffi::WalletSigner, +) -> Result { + check_key_id(signature_public_key_id, &key)?; + let handle = SignerHandle(signer); + let sign_fn = |key_id: u32, digest: [u8; 32]| handle.sign(key_id, digest); + st::build_profile( + identity_id, + identity_contract_nonce, + display_name, + public_message, + avatar_url, + avatar_hash, + avatar_fingerprint, + revision, + has_existing_doc_id.then_some(existing_document_id), + entropy, + &key_info(&key), + &sign_fn, + ) + .map(ffi_built) +} + +#[allow(clippy::too_many_arguments)] +fn st_build_contact_request( + identity_id: &[u8], + identity_contract_nonce: u64, + to_user_id: &[u8], + encrypted_public_key: &[u8], + sender_key_index: u32, + recipient_key_index: u32, + account_reference: u32, + encrypted_account_label: &[u8], + entropy: &[u8], + signature_public_key_id: u32, + key: ffi::FfiIdentityKey, + signer: &ffi::WalletSigner, +) -> Result { + check_key_id(signature_public_key_id, &key)?; + let handle = SignerHandle(signer); + let sign_fn = |key_id: u32, digest: [u8; 32]| handle.sign(key_id, digest); + st::build_contact_request( + identity_id, + identity_contract_nonce, + to_user_id, + encrypted_public_key, + sender_key_index, + recipient_key_index, + account_reference, + encrypted_account_label, + entropy, + &key_info(&key), + &sign_fn, + ) + .map(ffi_built) +} + +#[allow(clippy::too_many_arguments)] +fn st_build_identity_create( + is_instant: bool, + transaction: &[u8], + instant_lock: &[u8], + output_index: u32, + core_chain_locked_height: u32, + out_point: &[u8], + keys: Vec, + signer: &ffi::WalletSigner, +) -> Result { + let proof = if is_instant { + st::AssetLockProofInput::Instant { + transaction: transaction.to_vec(), + instant_lock: instant_lock.to_vec(), + output_index, + } + } else { + st::AssetLockProofInput::Chain { + core_chain_locked_height, + out_point: out_point + .try_into() + .map_err(|_| format!("outpoint must be 36 bytes, got {}", out_point.len()))?, + } + }; + let keys: Vec = keys + .into_iter() + .map(|key| st::NewIdentityKey { + id: key.id, + purpose: key.purpose, + security_level: key.security_level, + pubkey: key.pubkey, + }) + .collect(); + let handle = SignerHandle(signer); + let sign_fn = |key_id: u32, digest: [u8; 32]| handle.sign(key_id, digest); + st::build_identity_create(proof, &keys, &sign_fn).map(ffi_built) +} diff --git a/packages/rs-platform-cxx/src/provider.rs b/packages/rs-platform-cxx/src/provider.rs new file mode 100644 index 0000000000..733e540d1a --- /dev/null +++ b/packages/rs-platform-cxx/src/provider.rs @@ -0,0 +1,181 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! `dash_context_provider::ContextProvider` backed by node-local state. +//! +//! `FromProof` verification (drive-proof-verifier) resolves everything it +//! needs about the network through this trait: the BLS public key of the +//! quorum that signed a proof, and the data contracts referenced by document +//! queries. The embedder serves both from local knowledge: quorum keys are +//! pushed across the bridge from synced LLMQ data, and the supported document +//! queries use the pinned DPNS and DashPay system contracts compiled into dpp. +//! Proof verification therefore never performs network fetches. +//! +//! The provider is process-global and supports one Platform network at a time. + +use std::collections::HashMap; +use std::sync::{Arc, OnceLock, RwLock}; + +use dash_context_provider::{ContextProvider, ContextProviderError}; +use dpp::dashcore::Network; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::TokenConfiguration; +use dpp::prelude::{CoreBlockHeight, DataContract, Identifier}; +use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; +use platform_version::version::PlatformVersion; + +/// A quorum public key pushed from the node's LLMQ store. `quorum_hash` is +/// in the byte order DAPI proofs carry it (display order — the reverse of +/// the embedding application's internal hash order; the C++ adapter converts). +pub struct QuorumKey { + pub quorum_hash: [u8; 32], + pub public_key: [u8; 48], +} + +#[derive(Default)] +struct State { + /// (quorum_type, quorum_hash in proof byte order) -> BLS public key. + quorum_keys: HashMap<(u32, [u8; 32]), [u8; 48]>, + network: Option, + /// Core height at which Platform activated (mn_rr). Embedders that do not + /// use queries requiring this value may set it to 0. + platform_activation_height: CoreBlockHeight, + /// Lazily loaded pinned system contracts (DPNS, DashPay). + contracts: HashMap>, +} + +/// Process-global provider instance. +pub struct LocalContextProvider { + state: RwLock, +} + +static PROVIDER: OnceLock = OnceLock::new(); + +/// The process-global provider handed to every FromProof call. +pub fn provider() -> &'static LocalContextProvider { + PROVIDER.get_or_init(|| LocalContextProvider { + state: RwLock::new(State::default()), + }) +} + +/// Replaces the stored key set of `quorum_type` with `keys`. The C++ client +/// pushes the full active Platform-LLMQ set on every masternode-list / +/// quorum update, so replacement (not merge) keeps rotated-out quorums from +/// verifying new proofs forever. +pub fn update_quorum_keys(quorum_type: u8, keys: Vec) { + let mut state = provider().state.write().expect("provider lock poisoned"); + state + .quorum_keys + .retain(|(stored_type, _), _| *stored_type != u32::from(quorum_type)); + for key in keys { + state + .quorum_keys + .insert((u32::from(quorum_type), key.quorum_hash), key.public_key); + } +} + +/// Parses a Dash network id ("main", "test", "regtest", "devnet") into +/// the dashcore `Network` FromProof expects. +pub fn parse_network(network_id: &str) -> Result { + match network_id { + "main" => Ok(Network::Mainnet), + "test" => Ok(Network::Testnet), + "regtest" => Ok(Network::Regtest), + "devnet" => Ok(Network::Devnet), + other => Err(format!("unknown network id {other:?}")), + } +} + +/// Sets the network and the Platform activation core height (0 = unknown; +/// see `State::platform_activation_height`). +pub fn set_context(network_id: &str, platform_activation_height: u32) -> Result<(), String> { + let network = parse_network(network_id)?; + let mut state = provider().state.write().expect("provider lock poisoned"); + state.network = Some(network); + state.platform_activation_height = platform_activation_height; + Ok(()) +} + +/// The network set via `set_context`. +pub fn network() -> Result { + provider() + .state + .read() + .expect("provider lock poisoned") + .network + .ok_or_else(|| "platform bridge context not initialized (set_context)".to_string()) +} + +impl ContextProvider for LocalContextProvider { + fn get_data_contract( + &self, + id: &Identifier, + platform_version: &PlatformVersion, + ) -> Result>, ContextProviderError> { + if let Some(contract) = self + .state + .read() + .expect("provider lock poisoned") + .contracts + .get(id) + { + return Ok(Some(Arc::clone(contract))); + } + for system_contract in [SystemDataContract::DPNS, SystemDataContract::Dashpay] { + let contract = load_system_data_contract(system_contract, platform_version) + .map_err(|e| ContextProviderError::DataContractFailure(e.to_string()))?; + if contract.id() == *id { + let contract = Arc::new(contract); + self.state + .write() + .expect("provider lock poisoned") + .contracts + .insert(*id, Arc::clone(&contract)); + return Ok(Some(contract)); + } + } + Ok(None) + } + + fn get_token_configuration( + &self, + _token_id: &Identifier, + ) -> Result, ContextProviderError> { + Err(ContextProviderError::Generic( + "token configurations are not available through this binding".to_string(), + )) + } + + fn get_quorum_public_key( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + _core_chain_locked_height: u32, + ) -> Result<[u8; 48], ContextProviderError> { + // The locally synced LLMQ store only tracks currently valid quorums, + // so the requested core height adds nothing to the lookup: a proof + // signed by a quorum the node no longer knows fails verification. + self.state + .read() + .expect("provider lock poisoned") + .quorum_keys + .get(&(quorum_type, quorum_hash)) + .copied() + .ok_or_else(|| { + ContextProviderError::InvalidQuorum(format!( + "no locally known quorum of type {} with hash {}", + quorum_type, + hex::encode(quorum_hash) + )) + }) + } + + fn get_platform_activation_height(&self) -> Result { + Ok(self + .state + .read() + .expect("provider lock poisoned") + .platform_activation_height) + } +} diff --git a/packages/rs-platform-cxx/src/st.rs b/packages/rs-platform-cxx/src/st.rs new file mode 100644 index 0000000000..c56c6ae7e3 --- /dev/null +++ b/packages/rs-platform-cxx/src/st.rs @@ -0,0 +1,623 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! DPP state-transition construction and signing for C++ embedders, built on +//! the real rs-dpp builders. +//! +//! Signing is delegated through a callback so private keys stay in the +//! wallet: the callback receives the key id being signed plus the +//! double-SHA256 digest of the transition's signable bytes and must return +//! a 65-byte compact recoverable ECDSA signature - exactly what +//! `dpp::dashcore::signer::sign` would produce from the raw key +//! (`sign(data, key) == sign_hash(sha256d(data), key)`). + +use std::collections::BTreeMap; + +use dash_platform_queries::dashpay::{ + build_contact_request_document, ContactRequestDocumentParams, +}; +use dash_platform_queries::dpns_usernames::build_dpns_preorder_and_domain_documents; +use dash_platform_queries::transition::put_document::{ + ensure_entropy_matches_document_id, prepare_document_for_transition, +}; +use dpp::address_funds::AddressWitness; +use dpp::dashcore::consensus::Decodable; +use dpp::dashcore::hashes::{sha256, Hash}; +use dpp::dashcore::{InstantLock, OutPoint, Transaction}; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::DataContract; +use dpp::document::{Document, DocumentV0}; +use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; +use dpp::identity::signer::Signer; +use dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; +use dpp::identity::state_transition::asset_lock_proof::InstantAssetLockProof; +use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; +use dpp::platform_value::{BinaryData, Value}; +use dpp::prelude::{AssetLockProof, Identifier}; +use dpp::serialization::{PlatformSerializable, Signable}; +use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; +use dpp::state_transition::batch_transition::BatchTransition; +use dpp::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0; +use dpp::state_transition::public_key_in_creation::accessors::{ + IdentityPublicKeyInCreationV0Getters, IdentityPublicKeyInCreationV0Setters, +}; +use dpp::state_transition::public_key_in_creation::v0::IdentityPublicKeyInCreationV0; +use dpp::state_transition::StateTransition; +use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; +use dpp::util::hash::hash_double; +use dpp::util::strings::convert_to_homograph_safe_chars; +use dpp::ProtocolError; +use platform_version::version::PlatformVersion; + +use crate::types::{id32, BuiltTransition, KeyInfo}; + +/// Signs a 32-byte digest with the wallet key identified by `key_id`, +/// returning a 65-byte compact recoverable ECDSA signature, or `None` on +/// failure (locked wallet, unknown key). `ASSET_LOCK_KEY_ID` selects the +/// one-time asset-lock key of an identity registration. +pub type SignFn<'a> = &'a (dyn Fn(u32, [u8; 32]) -> Option> + Sync); + +/// Pseudo key id routed to the asset-lock one-time key. +pub const ASSET_LOCK_KEY_ID: u32 = u32::MAX; + +const COMPACT_SIG_SIZE: usize = 65; +const COMPRESSED_PUBKEY_SIZE: usize = 33; + +/// dpp async `Signer` backed by the digest callback. The async surface is +/// signature-only: the callback is invoked synchronously and the builders +/// are driven by `futures::executor::block_on` on the calling thread. +struct CallbackSigner<'a> { + sign_fn: SignFn<'a>, +} + +impl std::fmt::Debug for CallbackSigner<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("CallbackSigner") + } +} + +fn sign_digest(sign_fn: SignFn<'_>, key_id: u32, digest: [u8; 32]) -> Result, String> { + let signature = sign_fn(key_id, digest) + .ok_or("signing failed (wallet locked or key unavailable)".to_string())?; + if signature.len() != COMPACT_SIG_SIZE { + return Err(format!( + "unexpected signature size {} (want {COMPACT_SIG_SIZE})", + signature.len() + )); + } + Ok(signature) +} + +#[async_trait::async_trait] +impl Signer for CallbackSigner<'_> { + async fn sign( + &self, + key: &IdentityPublicKey, + data: &[u8], + ) -> Result { + sign_digest(self.sign_fn, key.id(), hash_double(data)) + .map(Into::into) + .map_err(ProtocolError::Generic) + } + + async fn sign_create_witness( + &self, + key: &IdentityPublicKey, + data: &[u8], + ) -> Result { + let signature = self.sign(key, data).await?; + Ok(AddressWitness::P2pkh { signature }) + } + + fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool { + true + } +} + +/// Reconstructs a dpp IdentityPublicKey from the flattened FFI form. +pub fn identity_key_from_info(info: &KeyInfo) -> Result { + Ok(IdentityPublicKey::V0(IdentityPublicKeyV0 { + id: info.id, + purpose: Purpose::try_from(info.purpose) + .map_err(|e| format!("bad key purpose {}: {e}", info.purpose))?, + security_level: SecurityLevel::try_from(info.security_level) + .map_err(|e| format!("bad key security level {}: {e}", info.security_level))?, + contract_bounds: None, + key_type: KeyType::try_from(info.key_type) + .map_err(|e| format!("bad key type {}: {e}", info.key_type))?, + read_only: info.read_only, + data: BinaryData::new(info.data.clone()), + disabled_at: info.disabled_at, + })) +} + +fn load_contract(contract: SystemDataContract) -> Result { + load_system_data_contract(contract, PlatformVersion::latest()) + .map_err(|e| format!("unable to load system data contract: {e}")) +} + +fn built(state_transition: &StateTransition) -> Result { + let bytes = state_transition + .serialize_to_bytes() + .map_err(|e| format!("unable to serialize state transition: {e}"))?; + let hash = sha256::Hash::hash(&bytes).to_byte_array(); + Ok(BuiltTransition { bytes, hash }) +} + +/// Builds and signs a single-document create batch transition from an +/// assembled document. The upstream put-document guards run first: the +/// entropy must rederive the document id (Drive recomputes it during +/// validation, so a mismatch is caught locally instead of after paying a +/// nonce bump), and the properties are sanitized for the document type. +fn build_document_create_transition( + contract: &DataContract, + document_type_name: &str, + document: Document, + identity_contract_nonce: u64, + entropy: [u8; 32], + key: &KeyInfo, + sign_fn: SignFn<'_>, +) -> Result { + use dpp::document::DocumentV0Getters; + + let version = PlatformVersion::latest(); + let document_type = contract + .document_type_for_name(document_type_name) + .map_err(|e| format!("unknown document type {document_type_name}: {e}"))?; + ensure_entropy_matches_document_id( + &contract.id(), + &document.owner_id(), + document_type_name, + &entropy, + document.id(), + ) + .map_err(|e| format!("{document_type_name}: {e}"))?; + let document_type_owned = contract + .document_type_cloned_for_name(document_type_name) + .map_err(|e| format!("unknown document type {document_type_name}: {e}"))?; + let document = prepare_document_for_transition(&document, &document_type_owned); + let identity_key = identity_key_from_info(key)?; + let signer = CallbackSigner { sign_fn }; + let state_transition = futures::executor::block_on( + BatchTransition::new_document_creation_transition_from_document( + document, + document_type, + entropy, + &identity_key, + identity_contract_nonce, + 0, + None, + &signer, + version, + None, + ), + ) + .map_err(|e| format!("unable to build {document_type_name} create transition: {e}"))?; + built(&state_transition) +} + +/// Builds and signs a single-document create batch transition from a +/// property map (documents without an upstream pure builder). +#[allow(clippy::too_many_arguments)] +fn build_document_create( + contract: SystemDataContract, + document_type_name: &str, + owner_id: [u8; 32], + identity_contract_nonce: u64, + properties: BTreeMap, + entropy: [u8; 32], + key: &KeyInfo, + sign_fn: SignFn<'_>, +) -> Result { + let contract = load_contract(contract)?; + let owner_id = Identifier::from(owner_id); + let document_id = + Document::generate_document_id_v0(&contract.id(), &owner_id, document_type_name, &entropy); + let document = Document::V0(DocumentV0 { + id: document_id, + owner_id, + properties, + ..Default::default() + }); + build_document_create_transition( + &contract, + document_type_name, + document, + identity_contract_nonce, + entropy, + key, + sign_fn, + ) +} + +/// The salted domain hash the DPNS preorder blinds the name behind: +/// sha256d(salt || ".dash"). Matches the preimage +/// `build_dpns_preorder_and_domain_documents` hashes into the preorder's +/// `saltedDomainHash` property. +fn salted_domain_hash(salt: [u8; 32], label: &str) -> [u8; 32] { + let normalized = convert_to_homograph_safe_chars(label); + let mut preimage = salt.to_vec(); + preimage.extend((normalized + ".dash").as_bytes()); + hash_double(&preimage) +} + +/// DPNS preorder create. The documents come from the upstream pure builder +/// (dash-platform-queries); the salted domain hash doubles as the document +/// entropy — it is already blinded and unique per (name, salt), so rebuilds +/// of the same registration stay byte-identical. +pub fn build_dpns_preorder( + identity_id: &[u8], + identity_contract_nonce: u64, + label: &str, + preorder_salt: &[u8], + key: &KeyInfo, + sign_fn: SignFn<'_>, +) -> Result { + let owner = id32(identity_id, "identity id")?; + let salt = id32(preorder_salt, "preorder salt")?; + let entropy = salted_domain_hash(salt, label); + let contract = load_contract(SystemDataContract::DPNS)?; + let (preorder, _domain) = build_dpns_preorder_and_domain_documents( + &contract, + Identifier::from(owner), + label, + entropy, + salt, + ) + .map_err(|e| format!("unable to build DPNS preorder document: {e}"))?; + build_document_create_transition( + &contract, + "preorder", + preorder, + identity_contract_nonce, + entropy, + key, + sign_fn, + ) +} + +/// DPNS domain create. The preorder salt is drawn fresh per registration +/// attempt, making it a suitable deterministic document entropy for the +/// paired domain create. The contested-name vote-resolution prefund is +/// computed by rs-dpp from the contested unique index of the DPNS domain +/// type during DocumentCreateTransition::from_document; no explicit +/// handling needed. +#[allow(clippy::too_many_arguments)] +pub fn build_dpns_domain( + identity_id: &[u8], + identity_contract_nonce: u64, + label: &str, + normalized_label: &str, + parent_domain: &str, + preorder_salt: &[u8], + key: &KeyInfo, + sign_fn: SignFn<'_>, +) -> Result { + if convert_to_homograph_safe_chars(label) != normalized_label { + return Err("normalized label does not match label".to_string()); + } + // The upstream builder registers under the "dash" TLD; that is the only + // parent domain that exists. + if parent_domain != "dash" { + return Err(format!("unsupported parent domain {parent_domain:?}")); + } + let owner = id32(identity_id, "identity id")?; + let salt = id32(preorder_salt, "preorder salt")?; + let contract = load_contract(SystemDataContract::DPNS)?; + let (_preorder, domain) = build_dpns_preorder_and_domain_documents( + &contract, + Identifier::from(owner), + label, + salt, + salt, + ) + .map_err(|e| format!("unable to build DPNS domain document: {e}"))?; + build_document_create_transition( + &contract, + "domain", + domain, + identity_contract_nonce, + salt, + key, + sign_fn, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn build_profile( + identity_id: &[u8], + identity_contract_nonce: u64, + display_name: &str, + public_message: &str, + avatar_url: &str, + avatar_hash: &[u8], + avatar_fingerprint: &[u8], + revision: u64, + existing_document_id: Option<&[u8]>, + entropy: &[u8], + key: &KeyInfo, + sign_fn: SignFn<'_>, +) -> Result { + let owner = id32(identity_id, "identity id")?; + let entropy = id32(entropy, "entropy")?; + if !avatar_hash.is_empty() && avatar_hash.len() != 32 { + return Err("avatar hash must be 32 bytes".to_string()); + } + if !avatar_fingerprint.is_empty() && avatar_fingerprint.len() != 8 { + return Err("avatar fingerprint must be 8 bytes".to_string()); + } + // All profile fields are optional in the DashPay contract; $createdAt / + // $updatedAt are assigned by the chain and never appear in the + // transition. + let mut properties = BTreeMap::new(); + if !display_name.is_empty() { + properties.insert( + "displayName".to_string(), + Value::Text(display_name.to_string()), + ); + } + if !public_message.is_empty() { + properties.insert( + "publicMessage".to_string(), + Value::Text(public_message.to_string()), + ); + } + if !avatar_url.is_empty() { + properties.insert("avatarUrl".to_string(), Value::Text(avatar_url.to_string())); + } + if !avatar_hash.is_empty() { + properties.insert( + "avatarHash".to_string(), + Value::Bytes32(id32(avatar_hash, "avatar hash")?), + ); + } + if !avatar_fingerprint.is_empty() { + properties.insert( + "avatarFingerprint".to_string(), + Value::Bytes(avatar_fingerprint.to_vec()), + ); + } + + let Some(existing_document_id) = existing_document_id else { + if revision != 1 { + return Err("profile create requires revision 1".to_string()); + } + return build_document_create( + SystemDataContract::Dashpay, + "profile", + owner, + identity_contract_nonce, + properties, + entropy, + key, + sign_fn, + ); + }; + + if revision < 2 { + return Err("profile replace requires revision > 1".to_string()); + } + let version = PlatformVersion::latest(); + let contract = load_contract(SystemDataContract::Dashpay)?; + let document_type = contract + .document_type_for_name("profile") + .map_err(|e| format!("unknown document type profile: {e}"))?; + let document = Document::V0(DocumentV0 { + id: Identifier::from(id32(existing_document_id, "existing document id")?), + owner_id: Identifier::from(owner), + properties, + revision: Some(revision), + ..Default::default() + }); + let identity_key = identity_key_from_info(key)?; + let signer = CallbackSigner { sign_fn }; + let state_transition = futures::executor::block_on( + BatchTransition::new_document_replacement_transition_from_document( + document, + document_type, + &identity_key, + identity_contract_nonce, + 0, + None, + &signer, + version, + None, + ), + ) + .map_err(|e| format!("unable to build profile replace transition: {e}"))?; + built(&state_transition) +} + +#[allow(clippy::too_many_arguments)] +pub fn build_contact_request( + identity_id: &[u8], + identity_contract_nonce: u64, + to_user_id: &[u8], + encrypted_public_key: &[u8], + sender_key_index: u32, + recipient_key_index: u32, + account_reference: u32, + encrypted_account_label: &[u8], + entropy: &[u8], + key: &KeyInfo, + sign_fn: SignFn<'_>, +) -> Result { + let owner = id32(identity_id, "identity id")?; + let to_user = id32(to_user_id, "to-user id")?; + let entropy = id32(entropy, "entropy")?; + let contract = load_contract(SystemDataContract::Dashpay)?; + // The upstream builder validates the crypto-material sizes and assembles + // the DIP-15 property map; $createdAt and $createdAtCoreBlockHeight are + // chain-assigned system fields, so they do not enter the transition. + let (document_id, properties) = build_contact_request_document( + &contract, + ContactRequestDocumentParams { + sender_id: Identifier::from(owner), + recipient_id: Identifier::from(to_user), + sender_key_index, + recipient_key_index, + account_reference, + encrypted_public_key: encrypted_public_key.to_vec(), + encrypted_account_label: (!encrypted_account_label.is_empty()) + .then(|| encrypted_account_label.to_vec()), + auto_accept_proof: None, + entropy, + }, + ) + .map_err(|e| format!("unable to build contact request document: {e}"))?; + let document = Document::V0(DocumentV0 { + id: document_id, + owner_id: Identifier::from(owner), + properties, + ..Default::default() + }); + build_document_create_transition( + &contract, + "contactRequest", + document, + identity_contract_nonce, + entropy, + key, + sign_fn, + ) +} + +/// Asset lock proof input for identity registration. +pub enum AssetLockProofInput { + Instant { + /// Serialized asset lock transaction. + transaction: Vec, + /// Serialized islock message. + instant_lock: Vec, + /// Index of the asset-lock OP_RETURN output in tx.vout. + output_index: u32, + }, + Chain { + core_chain_locked_height: u32, + /// txid || vout (LE u32), consensus encoding. + out_point: [u8; 36], + }, +} + +/// A public key to register with a new identity. The private key never +/// crosses; each key proves ownership through the signing callback. +pub struct NewIdentityKey { + pub id: u32, + pub purpose: u8, + pub security_level: u8, + /// Compressed secp256k1 public key (33 bytes). + pub pubkey: Vec, +} + +/// IdentityCreateTransition assembled manually so that both the identity +/// keys and the asset-lock one-time key stay behind the signing callback. +/// Mirrors rs-dpp `try_from_identity_with_signer_and_private_key`: all +/// per-key signatures and the outer asset-lock signature cover the same +/// signable bytes (key signatures, the outer signature and the identity id +/// are all excluded from the signable form). +pub fn build_identity_create( + proof: AssetLockProofInput, + keys: &[NewIdentityKey], + sign_fn: SignFn<'_>, +) -> Result { + if keys.is_empty() { + return Err("no identity keys provided".to_string()); + } + + let asset_lock_proof = match proof { + AssetLockProofInput::Instant { + transaction, + instant_lock, + output_index, + } => { + let transaction = Transaction::consensus_decode(&mut transaction.as_slice()) + .map_err(|e| format!("bad asset lock transaction: {e}"))?; + let instant_lock = InstantLock::consensus_decode(&mut instant_lock.as_slice()) + .map_err(|e| format!("bad instant lock: {e}"))?; + AssetLockProof::Instant(InstantAssetLockProof::new( + instant_lock, + transaction, + output_index, + )) + } + AssetLockProofInput::Chain { + core_chain_locked_height, + out_point, + } => { + let out_point = OutPoint::consensus_decode(&mut out_point.as_slice()) + .map_err(|e| format!("bad asset lock outpoint: {e}"))?; + AssetLockProof::Chain(ChainAssetLockProof { + core_chain_locked_height, + out_point, + }) + } + }; + + // rs-dpp registers Identity::public_keys() (a BTreeMap keyed by id), so + // the keys serialize in ascending id order with no duplicates. + let mut sorted: Vec<&NewIdentityKey> = keys.iter().collect(); + sorted.sort_by_key(|key| key.id); + let mut public_keys = Vec::with_capacity(sorted.len()); + for (i, key) in sorted.iter().enumerate() { + if i > 0 && key.id == sorted[i - 1].id { + return Err(format!("duplicate identity key id {}", key.id)); + } + if key.pubkey.len() != COMPRESSED_PUBKEY_SIZE { + return Err(format!( + "identity key {}: unexpected public key size {}", + key.id, + key.pubkey.len() + )); + } + public_keys.push( + IdentityPublicKeyInCreationV0 { + id: key.id, + key_type: KeyType::ECDSA_SECP256K1, + purpose: Purpose::try_from(key.purpose) + .map_err(|e| format!("identity key {}: bad purpose: {e}", key.id))?, + security_level: SecurityLevel::try_from(key.security_level) + .map_err(|e| format!("identity key {}: bad security level: {e}", key.id))?, + contract_bounds: None, + read_only: false, + data: BinaryData::new(key.pubkey.clone()), + signature: Default::default(), + } + .into(), + ); + } + + let identity_id = asset_lock_proof + .create_identifier() + .map_err(|e| format!("unable to derive identity id: {e}"))?; + let mut transition = IdentityCreateTransitionV0 { + public_keys, + asset_lock_proof, + user_fee_increase: 0, + identity_id, + ..Default::default() + }; + + // Every registered key proves ownership by signing the same digest as + // the asset-lock key: the double-SHA256 of the signable bytes. Key + // signatures are excluded from the signable form, so setting them does + // not change it. + let state_transition: StateTransition = transition.clone().into(); + let signable = state_transition + .signable_bytes() + .map_err(|e| format!("unable to compute signable bytes: {e}"))?; + let digest = hash_double(&signable); + for key in transition.public_keys.iter_mut() { + let signature = sign_digest(sign_fn, key.id(), digest) + .map_err(|e| format!("identity key {}: {e}", key.id()))?; + key.set_signature(BinaryData::new(signature)); + } + let mut state_transition: StateTransition = transition.into(); + let signature = sign_digest(sign_fn, ASSET_LOCK_KEY_ID, digest) + .map_err(|e| format!("asset lock key: {e}"))?; + if !state_transition.set_signature(BinaryData::new(signature)) { + return Err("unable to set asset lock signature".to_string()); + } + built(&state_transition) +} diff --git a/packages/rs-platform-cxx/src/types.rs b/packages/rs-platform-cxx/src/types.rs new file mode 100644 index 0000000000..0c0efddb9a --- /dev/null +++ b/packages/rs-platform-cxx/src/types.rs @@ -0,0 +1,112 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Plain-Rust result types shared by the core modules. The cxx bridge in +//! `lib.rs` converts these into the flat shared structs C++ sees. + +/// One identity public key, flattened for FFI use. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KeyInfo { + pub id: u32, + pub purpose: u8, + pub security_level: u8, + pub key_type: u8, + pub read_only: bool, + pub data: Vec, + pub disabled_at: Option, +} + +/// Decoded contested-resource vote state (getContestedResourceVoteState, +/// result type VoteTally with locked and abstaining tallies). +#[derive(Debug, Clone, Default)] +pub struct ContestedVoteState { + pub contest_found: bool, + /// identity -> votes + pub contenders: Vec<([u8; 32], Option)>, + pub abstain_votes: Option, + pub lock_votes: Option, + pub finished: bool, + pub locked: bool, + pub winner: Option<[u8; 32]>, + pub finished_at_time_ms: u64, +} + +/// Decoded identity. +#[derive(Debug, Clone)] +pub struct IdentityInfo { + pub id: [u8; 32], + pub balance: u64, + pub revision: u64, + pub keys: Vec, +} + +/// Decoded DPNS `domain` document fields exposed through this binding. +#[derive(Debug, Clone, Default)] +pub struct DpnsName { + pub label: String, + pub normalized_label: String, + /// normalizedParentDomainName ("dash") + pub parent_domain: String, + pub identity: [u8; 32], + pub document_id: [u8; 32], + pub owner_id: [u8; 32], +} + +/// Decoded DashPay `profile` document. +#[derive(Debug, Clone, Default)] +pub struct Profile { + pub document_id: [u8; 32], + pub owner_id: [u8; 32], + pub display_name: String, + pub public_message: String, + pub avatar_url: String, + pub avatar_hash: Vec, + pub avatar_fingerprint: Vec, + pub created_at: u64, + pub updated_at: u64, + pub revision: u64, +} + +/// Decoded DashPay `contactRequest` document. +#[derive(Debug, Clone, Default)] +pub struct ContactRequest { + pub document_id: [u8; 32], + pub owner_id: [u8; 32], + pub to_user_id: [u8; 32], + pub encrypted_public_key: Vec, + pub sender_key_index: u32, + pub recipient_key_index: u32, + pub account_reference: u32, + pub encrypted_account_label: Vec, + pub core_height_created_at: u32, + pub created_at: u64, +} + +/// The authenticated ResponseMetadata fields of a proved DAPI response. The +/// Tenderdash quorum signature covers all of them (they enter the StateId / +/// CanonicalVote sign bytes), so after `FromProof` verification succeeds the +/// C++ freshness tracker can trust them. +#[derive(Debug, Clone, Default)] +pub struct Meta { + pub height: u64, + pub core_chain_locked_height: u32, + pub time_ms: u64, + pub protocol_version: u32, + pub chain_id: String, +} + +/// A built, signed state transition. +#[derive(Debug, Clone)] +pub struct BuiltTransition { + /// Serialized signed state transition. + pub bytes: Vec, + /// sha256(bytes) - the wait handle for waitForStateTransitionResult. + pub hash: [u8; 32], +} + +pub fn id32(bytes: &[u8], what: &str) -> Result<[u8; 32], String> { + bytes + .try_into() + .map_err(|_| format!("{what} must be 32 bytes, got {}", bytes.len())) +} diff --git a/packages/rs-platform-cxx/src/verify.rs b/packages/rs-platform-cxx/src/verify.rs new file mode 100644 index 0000000000..19f8192729 --- /dev/null +++ b/packages/rs-platform-cxx/src/verify.rs @@ -0,0 +1,307 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Drive proved-response verification for the DAPI queries exposed by this +//! binding, built on drive-proof-verifier's `FromProof`. Each function takes +//! the exact protobuf request the transport sent plus the full protobuf +//! response it received, reconstructs the query from the request, replays the +//! GroveDB proof, and verifies the Tenderdash BLS quorum threshold signature +//! against the keys served by [`crate::provider`]. + +use platform_version::version::PlatformVersion; + +use crate::types::{ContestedVoteState, KeyInfo}; + +// --------------------------------------------------------------------------- +// FromProof-driven verification over (request bytes, response bytes). +// +// The C++ transport hands over the exact protobuf request it sent and the +// full protobuf response it received; drive-proof-verifier reconstructs the +// query from the request, replays the GroveDB proof, and verifies the +// Tenderdash quorum threshold signature against the quorum keys served by +// crate::provider. The returned Meta fields are authenticated by that +// signature. +// --------------------------------------------------------------------------- + +use dapi_grpc::platform::v0::get_documents_request::Version as GetDocumentsVersion; +use dapi_grpc::platform::v0::{ + GetContestedResourceVoteStateRequest, GetContestedResourceVoteStateResponse, + GetDocumentsRequest, GetDocumentsResponse, GetIdentityBalanceRequest, + GetIdentityBalanceResponse, GetIdentityByPublicKeyHashRequest, + GetIdentityByPublicKeyHashResponse, GetIdentityContractNonceRequest, + GetIdentityContractNonceResponse, GetIdentityKeysRequest, GetIdentityKeysResponse, + GetIdentityNonceRequest, GetIdentityNonceResponse, GetIdentityRequest, GetIdentityResponse, + ResponseMetadata, +}; +use dapi_grpc::platform::VersionedGrpcResponse; +use dash_context_provider::ContextProvider as _; +use dash_platform_queries::documents::document_query::verify_documents_response; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0; +use dpp::identity::Identity; +use dpp::voting::vote_info_storage::contested_document_vote_poll_winner_info::ContestedDocumentVotePollWinnerInfo as WinnerInfo; +use drive_proof_verifier::types::{ + Contenders, IdentityBalance, IdentityContractNonceFetcher, IdentityNonceFetcher, + IdentityPublicKeys, +}; +use drive_proof_verifier::FromProof; +use prost::Message; + +use crate::decode::identity_info; +use crate::provider::{network, provider}; +use crate::types::{IdentityInfo, Meta}; + +fn decode_message(bytes: &[u8], what: &str) -> Result { + T::decode(bytes).map_err(|e| format!("unable to decode {what}: {e}")) +} + +fn meta_from(mtd: &ResponseMetadata) -> Meta { + Meta { + height: mtd.height, + core_chain_locked_height: mtd.core_chain_locked_height, + time_ms: mtd.time_ms, + protocol_version: mtd.protocol_version, + chain_id: mtd.chain_id.clone(), + } +} + +/// The platform version the response claims to be produced under, falling +/// back to the crate's latest known version for responses from a newer +/// server. The claimed version is authenticated after the fact: it enters +/// the signed StateId, so a lie fails the quorum signature check. +fn response_version(response: &R) -> &'static PlatformVersion +where + ::Error: std::fmt::Display, +{ + response + .metadata() + .ok() + .and_then(|mtd| PlatformVersion::get(mtd.protocol_version).ok()) + .unwrap_or_else(PlatformVersion::latest) +} + +pub fn verify_get_identity_nonce( + request: &[u8], + response: &[u8], +) -> Result<(Option, Meta), String> { + let request: GetIdentityNonceRequest = decode_message(request, "GetIdentityNonceRequest")?; + let response: GetIdentityNonceResponse = decode_message(response, "GetIdentityNonceResponse")?; + let version = response_version(&response); + let (nonce, mtd, _) = IdentityNonceFetcher::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("identity nonce proof verification failed: {e}"))?; + Ok((nonce.map(|fetcher| fetcher.0), meta_from(&mtd))) +} + +pub fn verify_get_identity_contract_nonce( + request: &[u8], + response: &[u8], +) -> Result<(Option, Meta), String> { + let request: GetIdentityContractNonceRequest = + decode_message(request, "GetIdentityContractNonceRequest")?; + let response: GetIdentityContractNonceResponse = + decode_message(response, "GetIdentityContractNonceResponse")?; + let version = response_version(&response); + let (nonce, mtd, _) = IdentityContractNonceFetcher::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("identity contract nonce proof verification failed: {e}"))?; + Ok((nonce.map(|fetcher| fetcher.0), meta_from(&mtd))) +} + +/// getIdentity: one proof covering balance, revision and keys +/// (Drive::verify_full_identity_by_identity_id). `None` = proven absent. +pub fn verify_get_identity( + request: &[u8], + response: &[u8], +) -> Result<(Option, Meta), String> { + let request: GetIdentityRequest = decode_message(request, "GetIdentityRequest")?; + let response: GetIdentityResponse = decode_message(response, "GetIdentityResponse")?; + let version = response_version(&response); + let (identity, mtd, _) = + >::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("identity proof verification failed: {e}"))?; + Ok((identity.as_ref().map(identity_info), meta_from(&mtd))) +} + +/// getIdentityByPublicKeyHash: one proof resolving the unique key hash to +/// the full identity. `None` = no identity registered that key hash. +pub fn verify_get_identity_by_pubkey_hash( + request: &[u8], + response: &[u8], +) -> Result<(Option, Meta), String> { + let request: GetIdentityByPublicKeyHashRequest = + decode_message(request, "GetIdentityByPublicKeyHashRequest")?; + let response: GetIdentityByPublicKeyHashResponse = + decode_message(response, "GetIdentityByPublicKeyHashResponse")?; + let version = response_version(&response); + let (identity, mtd, _) = + >::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("identity-by-public-key-hash proof verification failed: {e}"))?; + Ok((identity.as_ref().map(identity_info), meta_from(&mtd))) +} + +/// getIdentityBalance. Not bridged to C++ (the client fetches balances via +/// the full identity); exercised by the vector tests to pin the shared +/// grovedb + quorum-signature pipeline against the fixture corpus. +pub fn verify_get_identity_balance( + request: &[u8], + response: &[u8], +) -> Result<(Option, Meta), String> { + let request: GetIdentityBalanceRequest = decode_message(request, "GetIdentityBalanceRequest")?; + let response: GetIdentityBalanceResponse = + decode_message(response, "GetIdentityBalanceResponse")?; + let version = response_version(&response); + let (balance, mtd, _) = IdentityBalance::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("identity balance proof verification failed: {e}"))?; + Ok((balance, meta_from(&mtd))) +} + +/// getIdentityKeys (all keys). Not bridged to C++ (see +/// `verify_get_identity_balance`). +pub fn verify_get_identity_keys( + request: &[u8], + response: &[u8], +) -> Result<(Option>, Meta), String> { + let request: GetIdentityKeysRequest = decode_message(request, "GetIdentityKeysRequest")?; + let response: GetIdentityKeysResponse = decode_message(response, "GetIdentityKeysResponse")?; + let version = response_version(&response); + let (keys, mtd, _) = IdentityPublicKeys::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("identity keys proof verification failed: {e}"))?; + let keys = keys.map(|keys| { + keys.values() + .flatten() + .map(crate::decode::key_info) + .collect::>() + }); + Ok((keys, meta_from(&mtd))) +} + +/// getDocuments: reconstructs the query from the request +/// (DocumentQuery::try_from_request), verifies the proof, and returns the +/// matched documents re-serialized in platform form (the input to the +/// decode_* functions). Absence (no matching documents) is an empty vector. +pub fn verify_get_documents( + request: &[u8], + response: &[u8], +) -> Result<(Vec>, Meta), String> { + let request: GetDocumentsRequest = decode_message(request, "GetDocumentsRequest")?; + let response: GetDocumentsResponse = decode_message(response, "GetDocumentsResponse")?; + let version = response_version(&response); + + let (contract_id_bytes, document_type_name) = match &request.version { + Some(GetDocumentsVersion::V0(v0)) => { + (v0.data_contract_id.clone(), v0.document_type.clone()) + } + Some(GetDocumentsVersion::V1(v1)) => { + (v1.data_contract_id.clone(), v1.document_type.clone()) + } + None => return Err("GetDocumentsRequest has no version set".to_string()), + }; + let contract_id = crate::types::id32(&contract_id_bytes, "contract id")?; + let contract = provider() + .get_data_contract(&contract_id.into(), version) + .map_err(|e| format!("unable to resolve data contract: {e}"))? + .ok_or("document queries support only the pinned system contracts")?; + + let (documents, mtd, _) = verify_documents_response( + request, + std::sync::Arc::clone(&contract), + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("document proof verification failed: {e}"))?; + + let document_type = contract + .document_type_for_name(&document_type_name) + .map_err(|e| format!("unknown document type {document_type_name}: {e}"))?; + let mut serialized = Vec::new(); + for document in documents.into_iter().flatten().filter_map(|(_, doc)| doc) { + serialized.push( + document + .serialize(document_type, &contract, version) + .map_err(|e| format!("unable to re-serialize verified document: {e}"))?, + ); + } + Ok((serialized, meta_from(&mtd))) +} + +/// getContestedResourceVoteState (VoteTally result type). The query shape — +/// contract, document type, index values, tally options, count — is +/// reconstructed from the request itself. +pub fn verify_get_contested_vote_state( + request: &[u8], + response: &[u8], +) -> Result<(ContestedVoteState, Meta), String> { + let request: GetContestedResourceVoteStateRequest = + decode_message(request, "GetContestedResourceVoteStateRequest")?; + let response: GetContestedResourceVoteStateResponse = + decode_message(response, "GetContestedResourceVoteStateResponse")?; + let version = response_version(&response); + let (contenders, mtd, _) = Contenders::maybe_from_proof_with_metadata( + request, + response, + network()?, + version, + provider(), + ) + .map_err(|e| format!("contested vote state proof verification failed: {e}"))?; + + let mut state = ContestedVoteState::default(); + if let Some(contenders) = contenders { + state.contest_found = true; + state.contenders = contenders + .contenders + .iter() + .map(|(id, contender)| (id.to_buffer(), contender.vote_tally())) + .collect(); + state.abstain_votes = contenders.abstain_vote_tally; + state.lock_votes = contenders.lock_vote_tally; + if let Some((winner_info, finalization_block)) = contenders.winner { + state.finished = true; + state.finished_at_time_ms = finalization_block.time_ms; + match winner_info { + WinnerInfo::WonByIdentity(id) => state.winner = Some(id.to_buffer()), + WinnerInfo::Locked => state.locked = true, + WinnerInfo::NoWinner => {} + } + } + } + Ok((state, meta_from(&mtd))) +} diff --git a/packages/rs-platform-cxx/standalone/.gitignore b/packages/rs-platform-cxx/standalone/.gitignore new file mode 100644 index 0000000000..b83d22266a --- /dev/null +++ b/packages/rs-platform-cxx/standalone/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/packages/rs-platform-cxx/standalone/Cargo.lock b/packages/rs-platform-cxx/standalone/Cargo.lock new file mode 100644 index 0000000000..2d58687a1d --- /dev/null +++ b/packages/rs-platform-cxx/standalone/Cargo.lock @@ -0,0 +1,3960 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue 0.0.18", +] + +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6712f9c6fd6785b3b270884e57c441c403dc5d7e19ca45368c97c7a1de3000ec" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.2.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ae7bad254120e9e4c63bafc385310756f90c484eac0e36b8317cf09cb92a77" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.9", +] + +[[package]] +name = "blsful" +version = "3.0.0" +source = "git+https://github.com/dashpay/agora-blsful?rev=0c34a7a488a0bd1c9a9a2196e793b303ad35c900#0c34a7a488a0bd1c9a9a2196e793b303ad35c900" +dependencies = [ + "anyhow", + "blstrs_plus", + "hex", + "hkdf", + "merlin", + "pairing", + "rand", + "rand_chacha", + "rand_core", + "serde", + "serde_bare", + "sha2", + "sha3", + "subtle", + "thiserror 2.0.20", + "uint-zigzag", + "vsss-rs", + "zeroize", +] + +[[package]] +name = "blst" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62dc83a094a71d43eeadd254b1ec2d24cb6a0bb6cadce00df51f0db594711a32" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "blstrs_plus" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a16dd4b0d6b4538e1fa0388843acb186363082713a8fc8416d802a04d013818" +dependencies = [ + "arrayref", + "blst", + "elliptic-curve", + "ff", + "group", + "pairing", + "rand_core", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cbindgen" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.9", + "rand_core", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.9", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cxx" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash 0.2.0", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "scratch", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dapi-grpc" +version = "4.2.0-dev.1" +dependencies = [ + "dash-platform-macros", + "futures-core", + "getrandom 0.2.17", + "platform-version", + "prost", + "tenderdash-proto", + "tonic", + "tonic-prost", + "tonic-prost-build", +] + +[[package]] +name = "dash-async" +version = "4.2.0-dev.1" +dependencies = [ + "futures", + "thiserror 2.0.20", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "dash-context-provider" +version = "4.2.0-dev.1" +dependencies = [ + "dash-async", + "dpp", + "drive", + "hex", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "dash-network" +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=173ffac0fdc0c73dda0626cf385bbcfcf2437aeb#173ffac0fdc0c73dda0626cf385bbcfcf2437aeb" +dependencies = [ + "bincode", + "bincode_derive", + "cbindgen", + "serde", +] + +[[package]] +name = "dash-platform-cxx" +version = "4.2.0-dev.1" +dependencies = [ + "async-trait", + "cxx", + "cxx-build", + "dapi-grpc", + "dash-context-provider", + "dash-platform-queries", + "dpp", + "drive-proof-verifier", + "futures", + "hex", + "platform-version", + "prost", +] + +[[package]] +name = "dash-platform-cxx-standalone" +version = "0.0.0" +dependencies = [ + "dash-platform-cxx", +] + +[[package]] +name = "dash-platform-macros" +version = "4.2.0-dev.1" +dependencies = [ + "heck", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dash-platform-queries" +version = "4.2.0-dev.1" +dependencies = [ + "ciborium", + "dapi-grpc", + "dash-context-provider", + "dash-platform-macros", + "dpp", + "drive", + "drive-proof-verifier", + "hex", + "thiserror 2.0.20", + "tracing", +] + +[[package]] +name = "dashcore" +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=173ffac0fdc0c73dda0626cf385bbcfcf2437aeb#173ffac0fdc0c73dda0626cf385bbcfcf2437aeb" +dependencies = [ + "anyhow", + "base64-compat", + "bech32 0.9.1", + "bincode", + "bincode_derive", + "bitvec", + "blake3", + "blsful", + "dash-network", + "dashcore-private", + "dashcore_hashes", + "ed25519-dalek", + "hex", + "hex_lit", + "rustversion", + "secp256k1", + "serde", + "thiserror 2.0.20", + "tracing", +] + +[[package]] +name = "dashcore-private" +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=173ffac0fdc0c73dda0626cf385bbcfcf2437aeb#173ffac0fdc0c73dda0626cf385bbcfcf2437aeb" + +[[package]] +name = "dashcore_hashes" +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=173ffac0fdc0c73dda0626cf385bbcfcf2437aeb#173ffac0fdc0c73dda0626cf385bbcfcf2437aeb" +dependencies = [ + "bincode", + "dashcore-private", + "serde", +] + +[[package]] +name = "dashpay-contract" +version = "4.2.0-dev.1" +dependencies = [ + "platform-value", + "platform-version", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "data-contracts" +version = "4.2.0-dev.1" +dependencies = [ + "dashpay-contract", + "dpns-contract", + "platform-value", + "platform-version", + "serde_json", + "thiserror 2.0.20", + "token-history-contract", + "withdrawals-contract", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl 2.1.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dpns-contract" +version = "4.2.0-dev.1" +dependencies = [ + "platform-value", + "platform-version", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "dpp" +version = "4.2.0-dev.1" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "bech32 0.11.1", + "bincode", + "bs58", + "byteorder", + "chrono", + "dashcore", + "data-contracts", + "derive_more 1.0.0", + "dpp-json-convertible-derive", + "env_logger", + "getrandom 0.2.17", + "hex", + "indexmap", + "integer-encoding", + "itertools 0.13.0", + "lazy_static", + "nohash-hasher", + "num_enum 0.7.6", + "once_cell", + "platform-serialization", + "platform-serialization-derive", + "platform-value", + "platform-version", + "platform-versioning", + "rand", + "regex", + "serde", + "serde_json", + "serde_repr", + "sha2", + "strum", + "thiserror 2.0.20", + "tracing", +] + +[[package]] +name = "dpp-json-convertible-derive" +version = "4.2.0-dev.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "drive" +version = "4.2.0-dev.1" +dependencies = [ + "bincode", + "byteorder", + "derive_more 1.0.0", + "dpp", + "grovedb", + "grovedb-costs", + "grovedb-epoch-based-storage-flags", + "grovedb-path", + "grovedb-version", + "hex", + "indexmap", + "integer-encoding", + "nohash-hasher", + "platform-version", + "sqlparser", + "thiserror 2.0.20", + "tracing", +] + +[[package]] +name = "drive-proof-verifier" +version = "4.2.0-dev.1" +dependencies = [ + "bincode", + "dapi-grpc", + "dash-context-provider", + "derive_more 1.0.0", + "dpp", + "drive", + "hex", + "indexmap", + "platform-serialization", + "tenderdash-abci", + "thiserror 2.0.20", + "tracing", +] + +[[package]] +name = "ed" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c8d6ea916fadcd87e3d1ff4802b696d717c83519b47e76f267ab77e536dd5a" +dependencies = [ + "ed-derive", + "thiserror 1.0.69", +] + +[[package]] +name = "ed-derive" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06a91d774f4b861acaa791bc6165e66d72d3a5d1aa85fc8c0956f5580f863161" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array 0.14.9", + "group", + "hkdf", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "tap", + "zeroize", +] + +[[package]] +name = "elliptic-curve-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de2b6fae800f08032a6ea32995b52925b1d451bff9d445c8ab2932323277faf" +dependencies = [ + "elliptic-curve", + "heapless", + "hex", + "multiexp", + "serde", + "zeroize", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "generic-array" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337d46834ee672ab3e48caca2cb0c78cc174fb12b3a68d0d88f99a0519a5e36e" +dependencies = [ + "rustversion", + "serde_core", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand", + "rand_core", + "rand_xorshift", + "subtle", +] + +[[package]] +name = "grovedb" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "bincode_derive", + "blake3", + "grovedb-bulk-append-tree", + "grovedb-costs", + "grovedb-dense-fixed-sized-merkle-tree", + "grovedb-element", + "grovedb-merk", + "grovedb-merkle-mountain-range", + "grovedb-path", + "grovedb-query", + "grovedb-version", + "hex", + "indexmap", + "integer-encoding", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-bulk-append-tree" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-dense-fixed-sized-merkle-tree", + "grovedb-merkle-mountain-range", + "grovedb-query", + "hex", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-costs" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "integer-encoding", + "intmap", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-dense-fixed-sized-merkle-tree" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-query", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-element" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "bincode_derive", + "grovedb-path", + "grovedb-version", + "hex", + "integer-encoding", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-epoch-based-storage-flags" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "grovedb-costs", + "hex", + "integer-encoding", + "intmap", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-merk" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "bincode_derive", + "blake3", + "byteorder", + "ed", + "grovedb-costs", + "grovedb-element", + "grovedb-path", + "grovedb-query", + "grovedb-version", + "grovedb-visualize", + "hex", + "indexmap", + "integer-encoding", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-merkle-mountain-range" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", +] + +[[package]] +name = "grovedb-path" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "hex", +] + +[[package]] +name = "grovedb-query" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "bincode", + "byteorder", + "ed", + "hex", + "indexmap", + "integer-encoding", + "thiserror 2.0.20", +] + +[[package]] +name = "grovedb-version" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "thiserror 2.0.20", + "versioned-feature-core", +] + +[[package]] +name = "grovedb-visualize" +version = "5.0.1" +source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +dependencies = [ + "hex", + "itertools 0.14.0", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "integer-encoding" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c00403deb17c3221a1fe4fb571b9ed0370b3dcd116553c77fa294a3d918699" + +[[package]] +name = "intmap" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e611826a1868311677fdcdfbec9e8621d104c732d080f546a854530232f0ee" +dependencies = [ + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lhash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744a4c881f502e98c2241d2e5f50040ac73b30194d64452bb6260393b53f0dc9" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multiexp" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec2ce93a6f06ac6cae04c1da3f2a6a24fcfc1f0eb0b4e0f3d302f0df45326cb" +dependencies = [ + "ff", + "group", + "rand_core", + "rustversion", + "std-shims", + "zeroize", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", + "rand", + "serde", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", + "rand", + "serde", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive 0.7.6", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "platform-serialization" +version = "4.2.0-dev.1" +dependencies = [ + "bincode", + "platform-version", +] + +[[package]] +name = "platform-serialization-derive" +version = "4.2.0-dev.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "virtue 0.0.17", +] + +[[package]] +name = "platform-value" +version = "4.2.0-dev.1" +dependencies = [ + "base64 0.22.1", + "bincode", + "bs58", + "ciborium", + "hex", + "indexmap", + "platform-serialization", + "platform-version", + "rand", + "serde", + "serde_json", + "thiserror 2.0.20", + "treediff", +] + +[[package]] +name = "platform-version" +version = "4.2.0-dev.1" +dependencies = [ + "bincode", + "grovedb-version", + "thiserror 2.0.20", + "versioned-feature-core", +] + +[[package]] +name = "platform-versioning" +version = "4.2.0-dev.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.119", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags 2.13.1", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.9", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bare" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c55386eed0f1ae957b091dc2ca8122f287b60c79c774cbe3d5f2b69fded660" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlparser" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0272b7bb0a225320170c99901b4b5fb3a4384e255a7f2cc228f61e2ba3893e75" +dependencies = [ + "log", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "std-shims" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227c4f8561598188d0df96dbe749824576174bba278b5b6bb2eacff1066067d0" +dependencies = [ + "hashbrown 0.16.1", + "rustversion", + "spin", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tenderdash-abci" +version = "1.5.1" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?tag=v1.5.1#fae47294618125fa9d69150e8a7a5b607af6867c" +dependencies = [ + "bytes", + "hex", + "lhash", + "semver", + "tenderdash-proto", + "thiserror 2.0.20", + "tracing", + "url", +] + +[[package]] +name = "tenderdash-proto" +version = "1.5.1" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?tag=v1.5.1#fae47294618125fa9d69150e8a7a5b607af6867c" +dependencies = [ + "bytes", + "chrono", + "derive_more 2.1.1", + "num-derive", + "num-traits", + "prost", + "subtle-encoding", + "tenderdash-proto-compiler", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "tenderdash-proto-compiler" +version = "1.5.1" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?tag=v1.5.1#fae47294618125fa9d69150e8a7a5b607af6867c" +dependencies = [ + "fs_extra", + "prost-build", + "regex", + "tempfile", + "ureq", + "walkdir", + "zip", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "token-history-contract" +version = "4.2.0-dev.1" +dependencies = [ + "platform-value", + "platform-version", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.119", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "treediff" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ce481b2b7c2534fe7b5242cccebf37f9084392665c6a3783c414a1bada5432" + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uint-zigzag" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61faa33dc26b2851a37da5390a1a4cac015887b1e97ecd77ce7b4f987431de9f" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "ureq" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +dependencies = [ + "base64 0.23.1", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "versioned-feature-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898c0ad500fdb1914df465a2c729fce33646ef65dfbbbd16a6d8050e0d2404df" + +[[package]] +name = "virtue" +version = "0.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7302ac74a033bf17b6e609ceec0f891ca9200d502d31f02dc7908d3d98767c9d" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "vsss-rs" +version = "5.1.0" +source = "git+https://github.com/dashpay/vsss-rs?branch=main#668f1406bf25a4b9a95cd97c9069f7a1632897c3" +dependencies = [ + "crypto-bigint", + "elliptic-curve", + "elliptic-curve-tools", + "generic-array 1.4.5", + "hex", + "num", + "rand_core", + "serde", + "sha3", + "subtle", + "zeroize", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "withdrawals-contract" +version = "4.2.0-dev.1" +dependencies = [ + "num_enum 0.5.11", + "platform-value", + "platform-version", + "serde", + "serde_json", + "serde_repr", + "thiserror 2.0.20", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/packages/rs-platform-cxx/standalone/Cargo.toml b/packages/rs-platform-cxx/standalone/Cargo.toml new file mode 100644 index 0000000000..d4ed8ef9ec --- /dev/null +++ b/packages/rs-platform-cxx/standalone/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dash-platform-cxx-standalone" +version = "0.0.0" +edition = "2021" +publish = false + +[workspace] + +[lib] +name = "dash_platform_cxx_bundle" +crate-type = ["staticlib"] + +[dependencies] +dash-platform-cxx = { path = ".." } + +[patch."https://github.com/dashpay/versioned-feature-core"] +versioned-feature-core = "1.0.0" diff --git a/packages/rs-platform-cxx/standalone/src/lib.rs b/packages/rs-platform-cxx/standalone/src/lib.rs new file mode 100644 index 0000000000..64b103ed15 --- /dev/null +++ b/packages/rs-platform-cxx/standalone/src/lib.rs @@ -0,0 +1 @@ +pub use dash_platform_cxx::*; diff --git a/packages/rs-platform-cxx/test-cxx-link.sh b/packages/rs-platform-cxx/test-cxx-link.sh new file mode 100755 index 0000000000..6f6b504778 --- /dev/null +++ b/packages/rs-platform-cxx/test-cxx-link.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -euo pipefail + +package_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +workspace_dir="$(cd "${package_dir}/../.." && pwd)" +target_dir="${CARGO_TARGET_DIR:-${workspace_dir}/target/platform-cxx-standalone}" +cxx="${CXX:-c++}" +manifest="${package_dir}/standalone/Cargo.toml" + +CARGO_TARGET_DIR="${target_dir}" cargo build --manifest-path "${manifest}" --locked + +stage_dir="$(mktemp -d "${TMPDIR:-/tmp}/dash-platform-cxx.XXXXXX")" +trap 'rm -rf "${stage_dir}"' EXIT + +CARGO_PROFILE=debug CARGO_TARGET_DIR="${target_dir}" \ + "${package_dir}/install.sh" "${stage_dir}" + +system_libs=(-lpthread -lm) +case "$(uname -s)" in + Darwin) system_libs+=(-framework CoreFoundation) ;; + Linux) system_libs+=(-ldl) ;; +esac + +"${cxx}" -std=c++20 -I"${stage_dir}/include" \ + "${package_dir}/tests/cxx_smoke.cc" \ + "${stage_dir}/lib/libdash_platform_cxx.a" \ + "${system_libs[@]}" -o "${stage_dir}/cxx_smoke" +"${stage_dir}/cxx_smoke" diff --git a/packages/rs-platform-cxx/test_data/dpp_identity_vectors.json b/packages/rs-platform-cxx/test_data/dpp_identity_vectors.json new file mode 100644 index 0000000000..b4e6cb3c4c --- /dev/null +++ b/packages/rs-platform-cxx/test_data/dpp_identity_vectors.json @@ -0,0 +1,52 @@ +{ + "platform_repo_tag": "v4.0.0", + "protocol_version": 12, + "identity": { + "serialized_hex": "00777777777777777777777777777777777777777777777777777777777777777703000000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0001000100020000002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f270002000201030100a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc000021035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c3001fd0000018bcfe5687bfd000000012a05f20003", + "id": "7777777777777777777777777777777777777777777777777777777777777777", + "balance": 5000000000, + "revision": 3, + "public_keys": [ + { + "id": 0, + "purpose": 0, + "security_level": 0, + "key_type": 0, + "read_only": false, + "data": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", + "disabled_at": null + }, + { + "id": 1, + "purpose": 0, + "security_level": 2, + "key_type": 0, + "read_only": false, + "data": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", + "disabled_at": null + }, + { + "id": 2, + "purpose": 1, + "security_level": 3, + "key_type": 0, + "read_only": false, + "data": "035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30", + "disabled_at": 1700000000123 + } + ], + "bounded_key_contract_id": "a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc" + }, + "identity_public_key": { + "serialized_hex": "000100020000002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2700", + "fields": { + "id": 1, + "purpose": 0, + "security_level": 2, + "key_type": 0, + "read_only": false, + "data": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", + "disabled_at": null + } + } +} diff --git a/packages/rs-platform-cxx/test_data/dpp_st_vectors.json b/packages/rs-platform-cxx/test_data/dpp_st_vectors.json new file mode 100644 index 0000000000..3b7a985942 --- /dev/null +++ b/packages/rs-platform-cxx/test_data/dpp_st_vectors.json @@ -0,0 +1,233 @@ +{ + "platform_repo_tag": "v4.0.0", + "protocol_version": 12, + "signature_scheme": "double-SHA256 of signable bytes; 65-byte compact recoverable ECDSA; header byte = 27 + recovery_id + 4 (compressed)", + "keys": { + "master": { + "id": 0, + "private_key_hex": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key_hex": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa" + }, + "high": { + "id": 1, + "private_key_hex": "2222222222222222222222222222222222222222222222222222222222222222", + "public_key_hex": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27" + }, + "asset_lock": { + "private_key_hex": "3333333333333333333333333333333333333333333333333333333333333333", + "public_key_hex": "023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1" + } + }, + "identity_create_instant": { + "signable_hex": "0300020000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa000100000200002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2700c60101aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000004ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf9bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc620300080001dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd0100000000ffffffff01a086010000000000016a00000000240101a0860100000000001976a914999999999999999999999999999999999999999988ac0000", + "digest_hex": "65e4f7fd5b7aed2f0eff726970885384934e992b069f69a691e2ca719e9bccb0", + "serialized_hex": "0300020000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa411f9d83c3c0beb114a82d086a738e3aa5b13a0e70b8add2c78ac3cb90e592bdc4ba299caa0e78460bd08c54b7fb8d7e78879907bd0fc54fb6bfcc69a540ea0de84d000100000200002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27411ffc748e31ff48cbcfc2fa304bee54fdb8eaf673d5ab2da8ceb48f8d57dbc83def2e6d9970e108dadf18567d26ecbea8ac457768dc92dc1bdefdf52faac9f7988200c60101aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000004ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf9bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc620300080001dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd0100000000ffffffff01a086010000000000016a00000000240101a0860100000000001976a914999999999999999999999999999999999999999988ac0000411f705a1b57222944f2ce9ea0d728edbc1312f244e326236946336c59e0f8b180ad732d88514d33de9a6f3687a789ae44649835c9f6c55a1e1bbcae6efc7f899b0c15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "transaction_hex": "0300080001dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd0100000000ffffffff01a086010000000000016a00000000240101a0860100000000001976a914999999999999999999999999999999999999999988ac", + "instant_lock_hex": "0101aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000004ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf9bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "output_index": 0, + "out_point_hex": "4ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf900000000", + "identity_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327" + }, + "identity_create_chain": { + "signable_hex": "0300020000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa000100000200002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2701fc00100590204ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf90000", + "digest_hex": "0a0a332c8bb7cb633707f8aa33ceffd667484e0fcf9d78f8025d3478f7c3b324", + "serialized_hex": "0300020000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa41200a0b660530dfce50a0b1638ec274f658d662c1755105cfcae5ad31f92af5b0622dee03deefd30f0fa679d729bc89b138e402fc72ad9eee102dafcf2c580ef218000100000200002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27411fcd6fc90f5a63a47a7dd2fd037a08570298c9aba925d2bbb2392f6a6f52cc109f23de5102d4b3d2e471a75c875f46dcf0d9ad5be3e6215c014150c77b0b8db62a01fc00100590204ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf900004120d275a61395575038d64f674b8fff1199abef5aae24ca7e560103a67cf71ea021080b97f7c4a7eea3fbe5045695404a766221556f283dcacc08b49beb9568a75915eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "core_chain_locked_height": 1050000, + "out_point_hex": "4ad31484097f16240507943e38ac9219114ede07c5794dc29314303070ea6bf900000000", + "identity_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327" + }, + "dpns_preorder": { + "signable_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100000001fae3f9bb1a28d38c18c65e78ae91730c667f388911988dce5c695f39cba97b1002087072656f72646572e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002d07513b62deb2a49393aef02a84a9ad522c0e4f4b9c88fcec184cce7108bc18011073616c746564446f6d61696e486173680c2d07513b62deb2a49393aef02a84a9ad522c0e4f4b9c88fcec184cce7108bc180000", + "digest_hex": "64ad2dc780ce0718457c2f3ae9b3a6c6c49605ad71ea977e7746c755108fd24b", + "serialized_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100000001fae3f9bb1a28d38c18c65e78ae91730c667f388911988dce5c695f39cba97b1002087072656f72646572e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002d07513b62deb2a49393aef02a84a9ad522c0e4f4b9c88fcec184cce7108bc18011073616c746564446f6d61696e486173680c2d07513b62deb2a49393aef02a84a9ad522c0e4f4b9c88fcec184cce7108bc1800000141209dcb265b869db7435ee79271ae2ded9fbda2c13478b2c65ca1b087c37226df0716917a41b8233a2b1982518e2d127bb9f2c654cfdb52e902233d2b4c85e8f552", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_contract_nonce": 2, + "entropy_hex": "2d07513b62deb2a49393aef02a84a9ad522c0e4f4b9c88fcec184cce7108bc18", + "salt_hex": "5555555555555555555555555555555555555555555555555555555555555555", + "label": "Alice", + "normalized_label": "a11ce", + "salted_domain_hash_hex": "2d07513b62deb2a49393aef02a84a9ad522c0e4f4b9c88fcec184cce7108bc18", + "document_id_hex": "fae3f9bb1a28d38c18c65e78ae91730c667f388911988dce5c695f39cba97b10", + "signature_public_key_id": 1 + }, + "dpns_domain_contested": { + "signable_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc32701000000018e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba87fb123406646f6d61696ee668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500555555555555555555555555555555555555555555555555555555555555555507056c6162656c1205416c6963650f6e6f726d616c697a65644c6162656c120561313163651a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6512046461736810706172656e74446f6d61696e4e616d651204646173680c7072656f7264657253616c740c5555555555555555555555555555555555555555555555555555555555555555077265636f726473160112086964656e746974791015eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270e737562646f6d61696e52756c65731601120f616c6c6f77537562646f6d61696e7313000112706172656e744e616d65416e644c6162656cfd00000004a817c80000", + "digest_hex": "cdddcc3a001075acb4a13546f4d5404df42ae5ee857f0641c8199ac64bd3a90f", + "serialized_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc32701000000018e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba87fb123406646f6d61696ee668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500555555555555555555555555555555555555555555555555555555555555555507056c6162656c1205416c6963650f6e6f726d616c697a65644c6162656c120561313163651a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6512046461736810706172656e74446f6d61696e4e616d651204646173680c7072656f7264657253616c740c5555555555555555555555555555555555555555555555555555555555555555077265636f726473160112086964656e746974791015eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270e737562646f6d61696e52756c65731601120f616c6c6f77537562646f6d61696e7313000112706172656e744e616d65416e644c6162656cfd00000004a817c800000141200307c3087cacd3b3aecbbefae2191c53f02e8b6b58efff84661df8a8ff674eff213f285ddd0f5030e9457d918a925bb1b6d2e3305605508ed26ec6e2fc17acf2", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_contract_nonce": 4660, + "entropy_hex": "5555555555555555555555555555555555555555555555555555555555555555", + "salt_hex": "5555555555555555555555555555555555555555555555555555555555555555", + "label": "Alice", + "normalized_label": "a11ce", + "document_id_hex": "8e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba87", + "signature_public_key_id": 1, + "contested": true, + "prefunded_voting_balance_credits": 20000000000 + }, + "dpns_domain": { + "signable_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc32701000000018e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba870506646f6d61696ee668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500555555555555555555555555555555555555555555555555555555555555555507056c6162656c12097175616e74756d34320f6e6f726d616c697a65644c6162656c12097175616e74756d34321a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6512046461736810706172656e74446f6d61696e4e616d651204646173680c7072656f7264657253616c740c5555555555555555555555555555555555555555555555555555555555555555077265636f726473160112086964656e746974791015eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270e737562646f6d61696e52756c65731601120f616c6c6f77537562646f6d61696e7313000000", + "digest_hex": "10b93c345995e3fca59bfbb45e6ecc0a794b50f0b9f42de9002d0f935c32a896", + "serialized_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc32701000000018e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba870506646f6d61696ee668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500555555555555555555555555555555555555555555555555555555555555555507056c6162656c12097175616e74756d34320f6e6f726d616c697a65644c6162656c12097175616e74756d34321a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6512046461736810706172656e74446f6d61696e4e616d651204646173680c7072656f7264657253616c740c5555555555555555555555555555555555555555555555555555555555555555077265636f726473160112086964656e746974791015eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270e737562646f6d61696e52756c65731601120f616c6c6f77537562646f6d61696e731300000001411f63a478bc0626c371ee445ba205244227b734d540a89278ca8cedbe9e48bd88bc215e9871ce443c04d1dacc91c080de6151da51be58225b908e59d33dc1e8c038", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_contract_nonce": 5, + "entropy_hex": "5555555555555555555555555555555555555555555555555555555555555555", + "salt_hex": "5555555555555555555555555555555555555555555555555555555555555555", + "label": "quantum42", + "normalized_label": "quantum42", + "document_id_hex": "8e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba87", + "signature_public_key_id": 1, + "contested": false + }, + "dashpay_profile_create": { + "signable_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100000001a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6030770726f66696c65a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc00e540d547ecac78cac1efd88fbced3d23aa8ae638ff1accdaa8a308337983eb09051161766174617246696e6765727072696e740a0899999999999999990a617661746172486173680c88888888888888888888888888888888888888888888888888888888888888880961766174617255726c121968747470733a2f2f6578616d706c652e636f6d2f612e706e670b646973706c61794e616d651213416c69636520d0b220576f6e6465726c616e640d7075626c69634d657373616765120e68656c6c6f20706c6174666f726d0000", + "digest_hex": "9e375dc31f1323adc4cc2e1de0eb5f8e0c6a26d26288b6e74f3b9cd68ea7afe0", + "serialized_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100000001a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6030770726f66696c65a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc00e540d547ecac78cac1efd88fbced3d23aa8ae638ff1accdaa8a308337983eb09051161766174617246696e6765727072696e740a0899999999999999990a617661746172486173680c88888888888888888888888888888888888888888888888888888888888888880961766174617255726c121968747470733a2f2f6578616d706c652e636f6d2f612e706e670b646973706c61794e616d651213416c69636520d0b220576f6e6465726c616e640d7075626c69634d657373616765120e68656c6c6f20706c6174666f726d000001411fd491cd71660321908cb4b911d103ec22252cb60f70f073f0d0468dce50ef31aa0de158d69459d3fdb4e177471ed19857cd5f504e2c8becd8fd6f8b57dd8dc455", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_contract_nonce": 3, + "entropy_hex": "e540d547ecac78cac1efd88fbced3d23aa8ae638ff1accdaa8a308337983eb09", + "document_id_hex": "a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6", + "signature_public_key_id": 1, + "display_name": "Alice в Wonderland", + "public_message": "hello platform", + "avatar_url": "https://example.com/a.png", + "avatar_hash_hex": "8888888888888888888888888888888888888888888888888888888888888888", + "avatar_fingerprint_hex": "9999999999999999" + }, + "dashpay_profile_replace": { + "signable_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100010001a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6040770726f66696c65a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc0002051161766174617246696e6765727072696e740a0899999999999999990a617661746172486173680c88888888888888888888888888888888888888888888888888888888888888880961766174617255726c121968747470733a2f2f6578616d706c652e636f6d2f612e706e670b646973706c61794e616d651213416c69636520d0b220576f6e6465726c616e640d7075626c69634d657373616765120f75706461746564206d65737361676500", + "digest_hex": "f3361516f52e272dfc6fdc75ea304f9c5035cf985550730a3704694cc0a755d1", + "serialized_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100010001a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6040770726f66696c65a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc0002051161766174617246696e6765727072696e740a0899999999999999990a617661746172486173680c88888888888888888888888888888888888888888888888888888888888888880961766174617255726c121968747470733a2f2f6578616d706c652e636f6d2f612e706e670b646973706c61794e616d651213416c69636520d0b220576f6e6465726c616e640d7075626c69634d657373616765120f75706461746564206d65737361676500014120fd2ec82c57e630ce2854a2ec05a14f36d186cb030707bd725b1ae6d79ce7a6194fa8edfba509bbc6f4f4a99a77270a34ec8c345234019078b793a4e5fe9cd33e", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_contract_nonce": 4, + "revision": 2, + "document_id_hex": "a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6", + "signature_public_key_id": 1, + "public_message": "updated message" + }, + "dashpay_contact_request": { + "signable_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327010000000148880e37b469540c0b4b8a6489d21938726e53ce7d337febb1f101da351f57fd060e636f6e7461637452657175657374a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc00fda853b87e9145263b6fcb17b4d8545a9462b4daf95a86194b5e8b5a3f74a22406106163636f756e745265666572656e636504fc0badc0de15656e637279707465644163636f756e744c6162656c0a30cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd12656e637279707465645075626c69634b65790a60abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab11726563697069656e744b6579496e64657804030e73656e6465724b6579496e646578040208746f55736572496410eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000", + "digest_hex": "46002d99e2fcdf47cddc3e3173b9ba9256af84e84a4b6e007b2ba4db848cab44", + "serialized_hex": "020115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327010000000148880e37b469540c0b4b8a6489d21938726e53ce7d337febb1f101da351f57fd060e636f6e7461637452657175657374a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc00fda853b87e9145263b6fcb17b4d8545a9462b4daf95a86194b5e8b5a3f74a22406106163636f756e745265666572656e636504fc0badc0de15656e637279707465644163636f756e744c6162656c0a30cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd12656e637279707465645075626c69634b65790a60abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab11726563697069656e744b6579496e64657804030e73656e6465724b6579496e646578040208746f55736572496410eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000001411fcbc9c34bb69e53b1a319499aada671d480cb8db8e33afd27a633ee21b98dd1c87c8d27c8e2a878edcee85d92fa614bd8c9c3724f86854971a5dadfa31d9e7c2e", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_contract_nonce": 6, + "entropy_hex": "fda853b87e9145263b6fcb17b4d8545a9462b4daf95a86194b5e8b5a3f74a224", + "document_id_hex": "48880e37b469540c0b4b8a6489d21938726e53ce7d337febb1f101da351f57fd", + "signature_public_key_id": 1, + "to_user_id_hex": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "encrypted_public_key_hex": "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "sender_key_index": 2, + "recipient_key_index": 3, + "account_reference": 195936478, + "encrypted_account_label_hex": "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + }, + "contested_labels": [ + { + "label": "alice", + "normalized": "a11ce", + "contested": true + }, + { + "label": "a11ce", + "normalized": "a11ce", + "contested": true + }, + { + "label": "bob", + "normalized": "b0b", + "contested": true + }, + { + "label": "b0b", + "normalized": "b0b", + "contested": true + }, + { + "label": "ab", + "normalized": "ab", + "contested": false + }, + { + "label": "abc", + "normalized": "abc", + "contested": true + }, + { + "label": "x2y", + "normalized": "x2y", + "contested": false + }, + { + "label": "up", + "normalized": "up", + "contested": false + }, + { + "label": "-ab-", + "normalized": "-ab-", + "contested": true + }, + { + "label": "aaaaaaaaaaaaaaaaaaa", + "normalized": "aaaaaaaaaaaaaaaaaaa", + "contested": true + }, + { + "label": "aaaaaaaaaaaaaaaaaaaa", + "normalized": "aaaaaaaaaaaaaaaaaaaa", + "contested": false + }, + { + "label": "quantum42", + "normalized": "quantum42", + "contested": false + }, + { + "label": "dash", + "normalized": "dash", + "contested": true + }, + { + "label": "test-name", + "normalized": "test-name", + "contested": true + }, + { + "label": "name2", + "normalized": "name2", + "contested": false + } + ], + "stored_documents": { + "domain": { + "serialized_hex": "028e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba8715eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327000100070000018bcfe568000000018bcfe568640000018bcfe568c800097175616e74756d3432097175616e74756d3432010464617368046461736800210115eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270100", + "id_hex": "8e59141aa8875fbe1b6a1089bef3366a9f8aeddb4debcea35905ced54228ba87", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "identity_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "label": "quantum42", + "normalized_label": "quantum42" + }, + "profile": { + "serialized_hex": "02a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d615eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc3270200030000018bcfe568000000018bcfe5692c011968747470733a2f2f6578616d706c652e636f6d2f612e706e67018888888888888888888888888888888888888888888888888888888888888888019999999999999999010f75706461746564206d6573736167650113416c69636520d0b220576f6e6465726c616e64", + "id_hex": "a682c7dff71614a696c989e14a94f7841106620522ede675ef453238751a21d6", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "revision": 2, + "display_name": "Alice в Wonderland", + "public_message": "updated message", + "avatar_url": "https://example.com/a.png", + "created_at": 1700000000000, + "updated_at": 1700000000300 + }, + "contact": { + "serialized_hex": "0248880e37b469540c0b4b8a6489d21938726e53ce7d337febb1f101da351f57fd15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc32700410000018bcfe56990001e8480eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab00000000000000020000000000000003000000000badc0de0130cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd00", + "id_hex": "48880e37b469540c0b4b8a6489d21938726e53ce7d337febb1f101da351f57fd", + "owner_id_hex": "15eccc3165b78a43d2a70bf8421d8e286bc6330790a4ef4ed09316f0eb0bc327", + "to_user_id_hex": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "sender_key_index": 2, + "recipient_key_index": 3, + "account_reference": 195936478, + "created_at": 1700000000400, + "core_height_created_at": 2000000 + } + } +} diff --git a/packages/rs-platform-cxx/test_data/drive_query_vectors.json b/packages/rs-platform-cxx/test_data/drive_query_vectors.json new file mode 100644 index 0000000000..ac2b81815b --- /dev/null +++ b/packages/rs-platform-cxx/test_data/drive_query_vectors.json @@ -0,0 +1,480 @@ +{ + "platform_repo_tag": "v4.0.0", + "grovedb_repo_tag": "v5.0.0", + "protocol_version": 12, + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777", + "queries": [ + { + "name": "identity_balance", + "query": { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777" + }, + "grovedb_proof_hex": "0100da01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a040160002d0401207777777777777777777777777777777777777777777777777777777777777777fd00000002540be400006ab14e39df7d262e55bae17e60bab5d996cb66d6ded567f4d445ba536eef3b931001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010160002f03207777777777777777777777777777777777777777777777777777777777777777000b03fd00000002540be4000000", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "60" + ], + "key": "7777777777777777777777777777777777777777777777777777777777777777", + "element": "03fd00000002540be40000" + } + ], + "expected": { + "balance": 5000000000 + } + }, + { + "name": "identity_revision", + "query": { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777" + }, + "grovedb_proof_hex": "01008d01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a0401200024020120777777777777777777777777777777777777777777777777777777777777777700a2f8eb7d55888b059806841f7a2e92d211d65049ed09fe2bab4d1cd1f72aa0e41001eb804ecd6886810d1d7c79a0f1b6b35791037224de02c64c11ce42338455528d1101012000490420777777777777777777777777777777777777777777777777777777777777777700050201018000aee59dc5acac5fe8d4b452c1fd0f22601b6c1d876e28d0e3512a2ff39d18bcee012077777777777777777777777777777777777777777777777777777777777777770054017b204423f6e41b597890ac921d8f72453128cad7e268750f6fc87d648bcc72aa02cbeddd5e01bcb90c5002f6a710bc7957af406d2a6a9df600dbfa36951abf3534100301c0000b00080000000000000003001100", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "20", + "7777777777777777777777777777777777777777777777777777777777777777" + ], + "key": "c0", + "element": "0008000000000000000300" + } + ], + "expected": { + "revision": 3 + } + }, + { + "name": "identity_nonce", + "query": { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777" + }, + "grovedb_proof_hex": "01008d01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a0401200024020120777777777777777777777777777777777777777777777777777777777777777700a2f8eb7d55888b059806841f7a2e92d211d65049ed09fe2bab4d1cd1f72aa0e41001eb804ecd6886810d1d7c79a0f1b6b35791037224de02c64c11ce42338455528d1101012000490420777777777777777777777777777777777777777777777777777777777777777700050201018000aee59dc5acac5fe8d4b452c1fd0f22601b6c1d876e28d0e3512a2ff39d18bcee01207777777777777777777777777777777777777777777777777777777777777777007601b44b3a90d97444a14074c2bf69ff971d88fd27e3ba2da8b11cf3910f087dedf6030140000b00080000000000000007001002cbeddd5e01bcb90c5002f6a710bc7957af406d2a6a9df600dbfa36951abf3534100185d286a688ac17d96cdb2880a8f7174e151dfb90742370effeeaae391ee359df1100", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "20", + "7777777777777777777777777777777777777777777777777777777777777777" + ], + "key": "40", + "element": "0008000000000000000700" + } + ], + "expected": { + "nonce": 7 + } + }, + { + "name": "identity_contract_nonce", + "query": { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777", + "contract_id": "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155" + }, + "grovedb_proof_hex": "01008d01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a0401200024020120777777777777777777777777777777777777777777777777777777777777777700a2f8eb7d55888b059806841f7a2e92d211d65049ed09fe2bab4d1cd1f72aa0e41001eb804ecd6886810d1d7c79a0f1b6b35791037224de02c64c11ce42338455528d1101012000490420777777777777777777777777777777777777777777777777777777777777777700050201018000aee59dc5acac5fe8d4b452c1fd0f22601b6c1d876e28d0e3512a2ff39d18bcee0120777777777777777777777777777777777777777777777777777777777777777700af0401200024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155008b708962e5e42245c8220f3a06dbcc9ba3a7e655df4fed3dba6e8cff9426f43d024e6a60642818a48d5f2e7c3647a08b69f48d05941c95aaa24536b810b3c49b681002cbeddd5e01bcb90c5002f6a710bc7957af406d2a6a9df600dbfa36951abf3534100185d286a688ac17d96cdb2880a8f7174e151dfb90742370effeeaae391ee359df1101012000490420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500050201010000411ce8bb44ce6a4f4169880f3111dbad167ccd51ab08199b006247e83b1fd1510120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c531550010030100000b0008000000000000000b0000", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "20", + "7777777777777777777777777777777777777777777777777777777777777777", + "20", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155" + ], + "key": "00", + "element": "0008000000000000000b00" + } + ], + "expected": { + "nonce": 11 + } + }, + { + "name": "identity_keys", + "query": { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777" + }, + "grovedb_proof_hex": "01008d01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a0401200024020120777777777777777777777777777777777777777777777777777777777777777700a2f8eb7d55888b059806841f7a2e92d211d65049ed09fe2bab4d1cd1f72aa0e41001eb804ecd6886810d1d7c79a0f1b6b35791037224de02c64c11ce42338455528d1101012000490420777777777777777777777777777777777777777777777777777777777777777700050201018000aee59dc5acac5fe8d4b452c1fd0f22601b6c1d876e28d0e3512a2ff39d18bcee01207777777777777777777777777777777777777777777777777777777777777777006e017b204423f6e41b597890ac921d8f72453128cad7e268750f6fc87d648bcc72aa04018000050201010100d8e4a96433656c05d1cfef00c047f123db4140e64fd3e509993825149a64a5d0100185d286a688ac17d96cdb2880a8f7174e151dfb90742370effeeaae391ee359df1101018000a1030100002d002a0000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0000030101002d002a000100020000002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27000010030102003600330002010300000021035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c3001fd0000018bcfe5687b001100", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "20", + "7777777777777777777777777777777777777777777777777777777777777777", + "80" + ], + "key": "00", + "element": "002a0000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0000" + }, + { + "path": [ + "20", + "7777777777777777777777777777777777777777777777777777777777777777", + "80" + ], + "key": "01", + "element": "002a000100020000002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f270000" + }, + { + "path": [ + "20", + "7777777777777777777777777777777777777777777777777777777777777777", + "80" + ], + "key": "02", + "element": "00330002010300000021035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c3001fd0000018bcfe5687b00" + } + ], + "expected": { + "serialized_keys": [ + "0000000000000021034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00", + "000100020000002102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2700", + "0002010300000021035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c3001fd0000018bcfe5687b" + ] + } + }, + { + "name": "identity_by_public_key_hash_present", + "query": { + "public_key_hash": "abababababababababababababababababababab" + }, + "grovedb_proof_hex": "0100810401180018020114abababababababababababababababababababab002b071a135a82f6729fb656781458f3bd429c7f938b2d2f7822dd9f8680b3e65c02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001eb804ecd6886810d1d7c79a0f1b6b35791037224de02c64c11ce42338455528d11010118003b0314abababababababababababababababababababab0023002077777777777777777777777777777777777777777777777777777777777777770000", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "18" + ], + "key": "abababababababababababababababababababab", + "element": "0020777777777777777777777777777777777777777777777777777777777777777700" + } + ], + "expected": { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777" + } + }, + { + "name": "identity_by_public_key_hash_absent", + "query": { + "public_key_hash": "0101010101010101010101010101010101010101" + }, + "grovedb_proof_hex": "0100810401180018020114abababababababababababababababababababab002b071a135a82f6729fb656781458f3bd429c7f938b2d2f7822dd9f8680b3e65c02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001eb804ecd6886810d1d7c79a0f1b6b35791037224de02c64c11ce42338455528d1101011800360514abababababababababababababababababababab6fe7c7f40ee008e6d6b5428b0bb240037607f9cd0ce8aa02ff921891148c977d00", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [], + "expected": { + "identity_id": null + } + }, + { + "name": "dpns_exact", + "grovedb_proof_hex": "0100d101039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f237100401400024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500fbda163cb3ac4d5ca76ccab587620f53bdcbe46a96dbcb1e0ed411ace9395b2202dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010140006b013af9b5c483d29ba0cb144b3c8d0b00acb6894c2ba54526ee525ecba262f25bbe0420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155000502010101005dfffca61718377c8e28755c8c4c41cd3281e43700cc17ef2fb2ce858f8d9e08100120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002f040101000a020106646f6d61696e00aef97d6723e7dfdd1a37d4051a65d1851a7183ee391f51b154add1042479806001010100480406646f6d61696e001e02011a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6500c0db47fafbb7b914aca5112d697ebb013ac3f9de326d8135d68029a191e89cb20106646f6d61696e008a01bb6f7c477bd80200888c4db39eee8829d38f1ab953cc25c13f82c05fc5b25f32041a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6500080201046461736800aeb4ddee9352a25186598ca471577e45325bd9cf1b02c80bb31d03ab58ba336810014fd3d5c7b045c62f2ac81c9f51f067a4450901d41999b0f82a3f15b103df4a6a11011a6e6f726d616c697a6564506172656e74446f6d61696e4e616d65003b040464617368001302010f6e6f726d616c697a65644c6162656c000127fd05259b354ce1f58158158364d18a5a6c9f07227e9170a8ebda0e5e7fa3010464617368003c040f6e6f726d616c697a65644c6162656c0009020105616c696365001e7beb5cde43e295777634dc595f1536f62e2885343dca251fd9c3551285b8f6010f6e6f726d616c697a65644c6162656c002e0405616c69636500050201010000ecb69a79bd697e495ef181171cb50b0443bdc72b78e3c0446e75ae9f68ffeb2c0105616c696365003c0601000017001470726f7665642d64706e732d646f63756d656e7400a711904e44b1b6aec95a008065047732ea534162d3e3e0934617a6faa218739500", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "40", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "01", + "646f6d61696e", + "6e6f726d616c697a6564506172656e74446f6d61696e4e616d65", + "64617368", + "6e6f726d616c697a65644c6162656c", + "616c696365" + ], + "key": "00", + "element": "001470726f7665642d64706e732d646f63756d656e7400" + } + ], + "expected": { + "documents": [ + "70726f7665642d64706e732d646f63756d656e74" + ] + } + }, + { + "name": "dpns_prefix", + "grovedb_proof_hex": "0100d101039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f237100401400024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500fbda163cb3ac4d5ca76ccab587620f53bdcbe46a96dbcb1e0ed411ace9395b2202dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010140006b013af9b5c483d29ba0cb144b3c8d0b00acb6894c2ba54526ee525ecba262f25bbe0420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155000502010101005dfffca61718377c8e28755c8c4c41cd3281e43700cc17ef2fb2ce858f8d9e08100120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002f040101000a020106646f6d61696e00aef97d6723e7dfdd1a37d4051a65d1851a7183ee391f51b154add1042479806001010100480406646f6d61696e001e02011a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6500c0db47fafbb7b914aca5112d697ebb013ac3f9de326d8135d68029a191e89cb20106646f6d61696e008a01bb6f7c477bd80200888c4db39eee8829d38f1ab953cc25c13f82c05fc5b25f32041a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6500080201046461736800aeb4ddee9352a25186598ca471577e45325bd9cf1b02c80bb31d03ab58ba336810014fd3d5c7b045c62f2ac81c9f51f067a4450901d41999b0f82a3f15b103df4a6a11011a6e6f726d616c697a6564506172656e74446f6d61696e4e616d65003b040464617368001302010f6e6f726d616c697a65644c6162656c000127fd05259b354ce1f58158158364d18a5a6c9f07227e9170a8ebda0e5e7fa3010464617368003c040f6e6f726d616c697a65644c6162656c0009020105616c696365001e7beb5cde43e295777634dc595f1536f62e2885343dca251fd9c3551285b8f6010f6e6f726d616c697a65644c6162656c002e0405616c69636500050201010000ecb69a79bd697e495ef181171cb50b0443bdc72b78e3c0446e75ae9f68ffeb2c0105616c696365003c0601000017001470726f7665642d64706e732d646f63756d656e7400a711904e44b1b6aec95a008065047732ea534162d3e3e0934617a6faa218739500", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "40", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "01", + "646f6d61696e", + "6e6f726d616c697a6564506172656e74446f6d61696e4e616d65", + "64617368", + "6e6f726d616c697a65644c6162656c", + "616c696365" + ], + "key": "00", + "element": "001470726f7665642d64706e732d646f63756d656e7400" + } + ], + "expected": { + "documents": [ + "70726f7665642d64706e732d646f63756d656e74" + ] + } + }, + { + "name": "dpns_by_identity", + "grovedb_proof_hex": "0100d101039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f237100401400024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500fbda163cb3ac4d5ca76ccab587620f53bdcbe46a96dbcb1e0ed411ace9395b2202dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010140006b013af9b5c483d29ba0cb144b3c8d0b00acb6894c2ba54526ee525ecba262f25bbe0420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155000502010101005dfffca61718377c8e28755c8c4c41cd3281e43700cc17ef2fb2ce858f8d9e08100120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002f040101000a020106646f6d61696e00aef97d6723e7dfdd1a37d4051a65d1851a7183ee391f51b154add1042479806001010100480406646f6d61696e001e02011a6e6f726d616c697a6564506172656e74446f6d61696e4e616d6500c0db47fafbb7b914aca5112d697ebb013ac3f9de326d8135d68029a191e89cb20106646f6d61696e009c01bb6f7c477bd80200888c4db39eee8829d38f1ab953cc25c13f82c05fc5b25f3202cec3da9112e0ef5667e59d93b88d238a89154e9e625b544c419a6455824d2a211004107265636f7264732e6964656e746974790024020120777777777777777777777777777777777777777777777777777777777777777700e1d05e567f8afe8c95122a35a3b9553f7120cd2407708db7af08ab1d90d6c51a1101107265636f7264732e6964656e74697479004904207777777777777777777777777777777777777777777777777777777777777777000502010100005379b2896c05a906d178f3dab0fe424000ef9e12147808701d6e30eef06425690120777777777777777777777777777777777777777777777777777777777777777700490401000024020120d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d100385ec810bbed70efa08b3e40b294d51af0bbe164a5266a5b54e18b8cb9e78566010100005b0620d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d10017001470726f7665642d64706e732d646f63756d656e7400a711904e44b1b6aec95a008065047732ea534162d3e3e0934617a6faa218739500", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "40", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "01", + "646f6d61696e", + "7265636f7264732e6964656e74697479", + "7777777777777777777777777777777777777777777777777777777777777777", + "00" + ], + "key": "d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1", + "element": "001470726f7665642d64706e732d646f63756d656e7400" + } + ], + "expected": { + "documents": [ + "70726f7665642d64706e732d646f63756d656e74" + ] + } + }, + { + "name": "dashpay_profile", + "grovedb_proof_hex": "0100d101039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f237100401400024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500fbda163cb3ac4d5ca76ccab587620f53bdcbe46a96dbcb1e0ed411ace9395b2202dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010140006b0420a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc000502010101005b6467dac53627bd556c06b7de5e97bd2d50688788cc29c251144d866715c4bf02b8d9d2e8f03bb4b317f5fc24e2741a89ea9f5bb6cb43a44ebbf99bf056fc6105100120a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc0030040101000b02010770726f66696c650084e1535a92501db14221531aacd7009467348130c1807153e9b3af041b75f61401010100520181acb67debb4c2c3aeaf6481bba22c23e59118b587c1adffcbe3c42e4754a6d5040770726f66696c650005020101000064064ce41f142aa0218b83251b9511d2d4affbb8012187cb1348f2062e61008710010770726f66696c65007202b077d63006fc15cfb2db05ddbee6d6dd7cf56291575215cee5216f7f1552a1250408246f776e657249640024020120777777777777777777777777777777777777777777777777777777777777777700282c1c78c773ac1bbcd84b55ad6bc8aa39653642a01744998b92045ce37949c7110108246f776e657249640049042077777777777777777777777777777777777777777777777777777777777777770005020101000071ea7f7cb98ab3eb3eae2895ce3a94c2714e806bb98c0e8c1177dde712022e8a01207777777777777777777777777777777777777777777777777777777777777777003f060100001a001770726f7665642d70726f66696c652d646f63756d656e7400da1581c51512f64b6e931eff13bd4fb05003f6befbbbc3b2962d208a582958df00", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "40", + "a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc", + "01", + "70726f66696c65", + "246f776e65724964", + "7777777777777777777777777777777777777777777777777777777777777777" + ], + "key": "00", + "element": "001770726f7665642d70726f66696c652d646f63756d656e7400" + } + ], + "expected": { + "documents": [ + "70726f7665642d70726f66696c652d646f63756d656e74" + ] + } + }, + { + "name": "dashpay_contacts_incoming", + "grovedb_proof_hex": "0100d101039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f237100401400024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500fbda163cb3ac4d5ca76ccab587620f53bdcbe46a96dbcb1e0ed411ace9395b2202dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010140006b0420a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc000502010101005b6467dac53627bd556c06b7de5e97bd2d50688788cc29c251144d866715c4bf02b8d9d2e8f03bb4b317f5fc24e2741a89ea9f5bb6cb43a44ebbf99bf056fc6105100120a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc0030040101000b02010770726f66696c650084e1535a92501db14221531aacd7009467348130c1807153e9b3af041b75f6140101010060040e636f6e7461637452657175657374000c020108246f776e6572496400c59531534d16dc4ec238c5c8a30fb6414b28cdbb977c82c77bfacf55ab8d3dcd028434a8d562d84df39d1971af05bd8fdd748581b35202e723eda1d4f737d7e93710010e636f6e74616374526571756573740094014b4c08659a34e5bccbbc817edffcec379dc3300ad974a50d539307d25bd505c202241ddcbc852479eec397f2fd32bcaf69ebadd79675ddbf92504d7e42436c1774100408746f5573657249640024020120777777777777777777777777777777777777777777777777777777777777777700902c3fed2d96edf795511e59795d4fa6fd6d36220a32a9aa13450e9562c5018c110108746f557365724964005204207777777777777777777777777777777777777777777777777777777777777777000e02010a2463726561746564417400c75709cd8917bb5116f06f15c1b88c22917b42e472c4f4ea2fecfe993291976b01207777777777777777777777777777777777777777777777777777777777777777003a040a24637265617465644174000c0201080000018bcfe56800005d8532c133405f01f9a65173c587d6a363a865b0c1e6fdc6a6b8b52d13f68575010a24637265617465644174003104080000018bcfe56800000502010100000dedbe3b16265a48243462cef6c2fbcdfcad43aed206f5310dbc84ad1a39d98901080000018bcfe5680000490401000024020120d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d300a2ab6c494b389894e8a4efe5a4e0673566880fd6a1d376962621108ced3d6120010100005e0620d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3001a001770726f7665642d636f6e746163742d646f63756d656e740007547e597229008c8eed5a86719458be289d9d088f8943866923c9c3cd230da400", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "40", + "a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc", + "01", + "636f6e7461637452657175657374", + "746f557365724964", + "7777777777777777777777777777777777777777777777777777777777777777", + "24637265617465644174", + "0000018bcfe56800", + "00" + ], + "key": "d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3", + "element": "001770726f7665642d636f6e746163742d646f63756d656e7400" + } + ], + "expected": { + "documents": [ + "70726f7665642d636f6e746163742d646f63756d656e74" + ] + } + }, + { + "name": "dashpay_contacts_outgoing", + "grovedb_proof_hex": "0100d101039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f237100401400024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315500fbda163cb3ac4d5ca76ccab587620f53bdcbe46a96dbcb1e0ed411ace9395b2202dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010140006b0420a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc000502010101005b6467dac53627bd556c06b7de5e97bd2d50688788cc29c251144d866715c4bf02b8d9d2e8f03bb4b317f5fc24e2741a89ea9f5bb6cb43a44ebbf99bf056fc6105100120a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc0030040101000b02010770726f66696c650084e1535a92501db14221531aacd7009467348130c1807153e9b3af041b75f6140101010060040e636f6e7461637452657175657374000c020108246f776e6572496400c59531534d16dc4ec238c5c8a30fb6414b28cdbb977c82c77bfacf55ab8d3dcd028434a8d562d84df39d1971af05bd8fdd748581b35202e723eda1d4f737d7e93710010e636f6e74616374526571756573740094014b4c08659a34e5bccbbc817edffcec379dc3300ad974a50d539307d25bd505c20408246f776e657249640024020120777777777777777777777777777777777777777777777777777777777777777700902c3fed2d96edf795511e59795d4fa6fd6d36220a32a9aa13450e9562c5018c1001dbbb47b9d5ef6862bed1e607946e8aea76b9fd7d77e408cb0d46ab6787d7bdb5110108246f776e65724964005204207777777777777777777777777777777777777777777777777777777777777777000e02010a2463726561746564417400c75709cd8917bb5116f06f15c1b88c22917b42e472c4f4ea2fecfe993291976b01207777777777777777777777777777777777777777777777777777777777777777003a040a24637265617465644174000c0201080000018bcfe56800005d8532c133405f01f9a65173c587d6a363a865b0c1e6fdc6a6b8b52d13f68575010a24637265617465644174003104080000018bcfe56800000502010100000dedbe3b16265a48243462cef6c2fbcdfcad43aed206f5310dbc84ad1a39d98901080000018bcfe5680000490401000024020120d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d300a2ab6c494b389894e8a4efe5a4e0673566880fd6a1d376962621108ced3d6120010100005e0620d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3001a001770726f7665642d636f6e746163742d646f63756d656e740007547e597229008c8eed5a86719458be289d9d088f8943866923c9c3cd230da400", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "40", + "a2a1b4ac6fef22ea2a1a68e8123644b357875f6b412c18109281c146e7b271bc", + "01", + "636f6e7461637452657175657374", + "246f776e65724964", + "7777777777777777777777777777777777777777777777777777777777777777", + "24637265617465644174", + "0000018bcfe56800", + "00" + ], + "key": "d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3", + "element": "001770726f7665642d636f6e746163742d646f63756d656e7400" + } + ], + "expected": { + "documents": [ + "70726f7665642d636f6e746163742d646f63756d656e74" + ] + } + }, + { + "name": "contested_vote_state_active", + "query": { + "normalized_label": "alice", + "count": 100 + }, + "grovedb_proof_hex": "0100b201039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a02dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1004017000050201016300ade908a28152707d22ed66d575d6f3ce38c27447c295a245272dc100dd0c4a621111010170002a040163000502010170001e5c60ed1b4311b6baf76a42f41550bcbba0f9f87d71b48e76d800c95f22cccc01016300490401700024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155005401be78056200f8a616eddb15fbd41fbc4204330111709350c20535528492b5010170004e0420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155000a020106646f6d61696e004412968f90b3c569b0e5b9760130f021137946563c0d05c6cb64233e4a35a57f0120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002f0406646f6d61696e00050201010100bba90275bf4b39ce726f641378ede6c4fcd17162611f992a34fea2ad303f4eeb0106646f6d61696e002d0401010008020104646173680060b4e46b6e0fca7c9c1634f80d1d3a18a9188165d64e81782800392ba3f08c5901010100310404646173680009020105616c6963650078f7ad26be337eb5f110a07a8d21736265aef4cb370c25b3aaa3f8d354ee1911010464617368006f0405616c6963650024020120777777777777777777777777777777777777777777777777777777777777777700ba2fead8aaa37d1be007db47f2e9779b773387336b1fcb738551a2a8ca295ef201a5ddbf84df65edf5876f0141adfa01d1988f011c18020496c3e93599f57dd5d5110105616c69636500fb016703200000000000000000000000000000000000000000000000000000000000000000001b0018000003fd0000018bcfe56800fc0001e078fc001e84760400000420000000000000000000000000000000000000000000000000000000000000000100050201010100ac8e80c26488285c9ef7ce4167825d9e9bef26e737bb7f7f1340d69af3a8eb5910042000000000000000000000000000000000000000000000000000000000000000020005020101010012ec6171d927bc5d9f132f407acd3ab0e75049f021389f6430dd8d4ba87ce26d1104207777777777777777777777777777777777777777777777777777777777777777000502010100008bbee91750be1729b067ae752c218d46345cee65af3996e02cc4aa079d9b26fd10042088888888888888888888888888888888888888888888888888888888888888880005020101000092fab297d83548fabdbefbad2d5e3c26a4005332274f31ff9cd51c9436d401971104200000000000000000000000000000000000000000000000000000000000000001006b1c01010025040120c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c10400c1d2a7d2d0355e6a6e662719e9e8475444c47022e4f1eddddfb5b45a8686acea006a29c24ef5eb604e398952790112a98973d5485490ab94ec88844dd50ac7525100200000000000000000000000000000000000000000000000000000000000000002006b1c01010025040120c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2060035878fa5d0c12a20364a12f71fc2124b8ddc22d9d81368bd79b20400da6eee8800efe92f879d4c44f631bb52241d7606b85bbbab020cb55fd4395a887bd97101db00207777777777777777777777777777777777777777777777777777777777777777008d025ef5cd4abe01c5cc27c851dca48ca596628dfc45b454ee847bcfc824f17312b61c01010025040120a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a10a00f5b15a04f171a7a3fe3cf9dc367b0bfe597fea84cd7239384715570ce4551fe800a706a5fc391f817996f9e274f0b37ff0b8a61b579bf079a8da92846c9252d5511100208888888888888888888888888888888888888888888888888888888888888888008d02e6ee602462484dae8b0c314ddc157efc77b293ea2350cb615962612be3d768bf1c01010025040120b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b104002bb7188bd669db6e98f44ee502c9742d85da29c4e7824b4fbd27f9587ad2758a009b9c598e017cb597ea60903480c868289dd951ce282f67f97459c79366a9d0d31100", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "70", + "63", + "70", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "646f6d61696e", + "01", + "64617368", + "616c696365" + ], + "key": "0000000000000000000000000000000000000000000000000000000000000000", + "element": "0018000003fd0000018bcfe56800fc0001e078fc001e8476040000" + }, + { + "path": [ + "70", + "63", + "70", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "646f6d61696e", + "01", + "64617368", + "616c696365", + "0000000000000000000000000000000000000000000000000000000000000001" + ], + "key": "01", + "element": "040120c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c10400" + }, + { + "path": [ + "70", + "63", + "70", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "646f6d61696e", + "01", + "64617368", + "616c696365", + "0000000000000000000000000000000000000000000000000000000000000002" + ], + "key": "01", + "element": "040120c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c20600" + }, + { + "path": [ + "70", + "63", + "70", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "646f6d61696e", + "01", + "64617368", + "616c696365", + "7777777777777777777777777777777777777777777777777777777777777777" + ], + "key": "01", + "element": "040120a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a10a00" + }, + { + "path": [ + "70", + "63", + "70", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "646f6d61696e", + "01", + "64617368", + "616c696365", + "8888888888888888888888888888888888888888888888888888888888888888" + ], + "key": "01", + "element": "040120b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b10400" + } + ], + "expected": { + "contenders": [ + { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777", + "votes": 5 + }, + { + "identity_id": "8888888888888888888888888888888888888888888888888888888888888888", + "votes": 2 + } + ], + "abstain_votes": 2, + "lock_votes": 3, + "finished": false + } + }, + { + "name": "contested_vote_state_finished", + "query": { + "normalized_label": "bob", + "count": 100 + }, + "grovedb_proof_hex": "0100b201039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a02dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1004017000050201016300ade908a28152707d22ed66d575d6f3ce38c27447c295a245272dc100dd0c4a621111010170002a040163000502010170001e5c60ed1b4311b6baf76a42f41550bcbba0f9f87d71b48e76d800c95f22cccc01016300490401700024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155005401be78056200f8a616eddb15fbd41fbc4204330111709350c20535528492b5010170004e0420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155000a020106646f6d61696e004412968f90b3c569b0e5b9760130f021137946563c0d05c6cb64233e4a35a57f0120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002f0406646f6d61696e00050201010100bba90275bf4b39ce726f641378ede6c4fcd17162611f992a34fea2ad303f4eeb0106646f6d61696e002d0401010008020104646173680060b4e46b6e0fca7c9c1634f80d1d3a18a9188165d64e81782800392ba3f08c5901010100310404646173680009020105616c6963650078f7ad26be337eb5f110a07a8d21736265aef4cb370c25b3aaa3f8d354ee1911010464617368006d0246f1e3eb733288ba8ee508b3b972872149b7ee87c6b0c05bbd4bb5982d93e7140403626f620024020120000000000000000000000000000000000000000000000000000000000000000000818e82b602d0fff0c045f136090f3816039b90af1ac6f33f3ec84deba88908d6110103626f6200fb018403200000000000000000000000000000000000000000000000000000000000000000016000fb015b00010400777777777777777777777777777777777777777777777777777777777777777702a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a104a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a20100888888888888888888888888888888888888888888888888888888888888888801b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1020101c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1020201c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c201fd0000018bcfe56800fc0001e078fc001e847604fd0000018bcfed0920fc0001e208fc001e847f04017777777777777777777777777777777777777777777777777777777777777777017777777777777777777777777777777777777777777777777777777777777777000000", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [ + { + "path": [ + "70", + "63", + "70", + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155", + "646f6d61696e", + "01", + "64617368", + "626f62" + ], + "key": "0000000000000000000000000000000000000000000000000000000000000000", + "element": "00fb015b00010400777777777777777777777777777777777777777777777777777777777777777702a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a104a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a20100888888888888888888888888888888888888888888888888888888888888888801b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1020101c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1020201c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c201fd0000018bcfe56800fc0001e078fc001e847604fd0000018bcfed0920fc0001e208fc001e847f040177777777777777777777777777777777777777777777777777777777777777770177777777777777777777777777777777777777777777777777777777777777770000" + } + ], + "stored_info_hex": "00010400777777777777777777777777777777777777777777777777777777777777777702a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a104a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a20100888888888888888888888888888888888888888888888888888888888888888801b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1020101c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1020201c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c201fd0000018bcfe56800fc0001e078fc001e847604fd0000018bcfed0920fc0001e208fc001e847f0401777777777777777777777777777777777777777777777777777777777777777701777777777777777777777777777777777777777777777777777777777777777700", + "expected": { + "contenders": [ + { + "identity_id": "7777777777777777777777777777777777777777777777777777777777777777", + "votes": 5 + }, + { + "identity_id": "8888888888888888888888888888888888888888888888888888888888888888", + "votes": 2 + } + ], + "abstain_votes": 2, + "lock_votes": 1, + "finished": true, + "winner_identity_id": "7777777777777777777777777777777777777777777777777777777777777777" + } + }, + { + "name": "contested_vote_state_absent", + "query": { + "normalized_label": "carol", + "count": 100 + }, + "grovedb_proof_hex": "0100b201039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a02dae5ae1465037089786f368eee27aa0437d77fbaf34d14dd299aae3d6a54168b1004017000050201016300ade908a28152707d22ed66d575d6f3ce38c27447c295a245272dc100dd0c4a621111010170002a040163000502010170001e5c60ed1b4311b6baf76a42f41550bcbba0f9f87d71b48e76d800c95f22cccc01016300490401700024020120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155005401be78056200f8a616eddb15fbd41fbc4204330111709350c20535528492b5010170004e0420e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155000a020106646f6d61696e004412968f90b3c569b0e5b9760130f021137946563c0d05c6cb64233e4a35a57f0120e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155002f0406646f6d61696e00050201010100bba90275bf4b39ce726f641378ede6c4fcd17162611f992a34fea2ad303f4eeb0106646f6d61696e002d0401010008020104646173680060b4e46b6e0fca7c9c1634f80d1d3a18a9188165d64e81782800392ba3f08c5901010100310404646173680009020105616c6963650078f7ad26be337eb5f110a07a8d21736265aef4cb370c25b3aaa3f8d354ee191101046461736800470246f1e3eb733288ba8ee508b3b972872149b7ee87c6b0c05bbd4bb5982d93e7140503626f62818e82b602d0fff0c045f136090f3816039b90af1ac6f33f3ec84deba88908d61100", + "expected_root_hash_hex": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "results": [], + "expected": { + "contest": null + } + } + ] +} diff --git a/packages/rs-platform-cxx/test_data/quorum_sig_vectors.json b/packages/rs-platform-cxx/test_data/quorum_sig_vectors.json new file mode 100644 index 0000000000..52ea2ca59f --- /dev/null +++ b/packages/rs-platform-cxx/test_data/quorum_sig_vectors.json @@ -0,0 +1,125 @@ +{ + "tenderdash_abci_tag": "v1.5.1", + "tenderdash_proto_tag": "v1.5.3", + "bls_scheme": "basic", + "vectors": [ + { + "name": "valid", + "app_hash": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "envelope": { + "grovedb_proof_hex": "0100da01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a040160002d0401207777777777777777777777777777777777777777777777777777777777777777fd00000002540be400006ab14e39df7d262e55bae17e60bab5d996cb66d6ded567f4d445ba536eef3b931001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010160002f03207777777777777777777777777777777777777777777777777777777777777777000b03fd00000002540be4000000", + "quorum_type": 106, + "quorum_hash": "01080f161d242b323940474e555c636a71787f868d949ba2a9b0b7bec5ccd3da", + "block_id_hash": "090c0f1215181b1e2124272a2d303336393c3f4245484b4e5154575a5d606366", + "round": 0, + "signature": "a4fca3d223938deeb9da82d0021a2cfb8cd7463412f7613c8943cd86a4f26b9aa81daf9d218c310ed3bb9e9c6a80b38118d16d27ff981dcc7ca46a65c95be36036eb0e1c4bc31801d2aa5699fc3d9932295f375bec3073e03c4279668429d319" + }, + "quorum_public_key": "b6087e2054b847ec64a1e18af3930d39fb040b73eb8a10620f9adcb1be85bf7d5ea961dc2458d601ba9003b1e39ead15", + "block_context": { + "height": 123456, + "round": 0, + "core_chain_locked_height": 2000000, + "time_ms": 1700000000000, + "protocol_version": 12, + "chain_id": "dash-testnet-51" + }, + "intermediates": { + "request_id": "e013e333e6ce445bc0979595c72f4e8e4da913804961d265c3aee4a33c33642b", + "state_id_bytes": "42090c000000000000001140e20100000000001a20dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab722580841e00290068e5cf8b010000", + "state_id_hash": "6ae0fe80f2f5e586528f2df34e13e14433fd5032b85ed2ad407142c30476d66e", + "canonical_vote_bytes": "0200000040e20100000000000000000000000000090c0f1215181b1e2124272a2d303336393c3f4245484b4e5154575a5d6063666ae0fe80f2f5e586528f2df34e13e14433fd5032b85ed2ad407142c30476d66e646173682d746573746e65742d3531", + "vote_hash": "a8ecf92e5990a7114be6090453907309ed6830aae1a940e8af93c25193c010ea", + "sign_digest": "304253ac4ac97e088ca8648ceb2d897e5867d24ced6cc25de51f102f27b3523f" + }, + "expected_valid": true + }, + { + "name": "tampered_signature", + "app_hash": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "envelope": { + "grovedb_proof_hex": "0100da01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a040160002d0401207777777777777777777777777777777777777777777777777777777777777777fd00000002540be400006ab14e39df7d262e55bae17e60bab5d996cb66d6ded567f4d445ba536eef3b931001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010160002f03207777777777777777777777777777777777777777777777777777777777777777000b03fd00000002540be4000000", + "quorum_type": 106, + "quorum_hash": "01080f161d242b323940474e555c636a71787f868d949ba2a9b0b7bec5ccd3da", + "block_id_hash": "090c0f1215181b1e2124272a2d303336393c3f4245484b4e5154575a5d606366", + "round": 0, + "signature": "a4fca3d223938deeb9da83d0021a2cfb8cd7463412f7613c8943cd86a4f26b9aa81daf9d218c310ed3bb9e9c6a80b38118d16d27ff981dcc7ca46a65c95be36036eb0e1c4bc31801d2aa5699fc3d9932295f375bec3073e03c4279668429d319" + }, + "quorum_public_key": "b6087e2054b847ec64a1e18af3930d39fb040b73eb8a10620f9adcb1be85bf7d5ea961dc2458d601ba9003b1e39ead15", + "block_context": { + "height": 123456, + "round": 0, + "core_chain_locked_height": 2000000, + "time_ms": 1700000000000, + "protocol_version": 12, + "chain_id": "dash-testnet-51" + }, + "expected_valid": false + }, + { + "name": "wrong_quorum_key", + "app_hash": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "envelope": { + "grovedb_proof_hex": "0100da01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a040160002d0401207777777777777777777777777777777777777777777777777777777777777777fd00000002540be400006ab14e39df7d262e55bae17e60bab5d996cb66d6ded567f4d445ba536eef3b931001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010160002f03207777777777777777777777777777777777777777777777777777777777777777000b03fd00000002540be4000000", + "quorum_type": 106, + "quorum_hash": "01080f161d242b323940474e555c636a71787f868d949ba2a9b0b7bec5ccd3da", + "block_id_hash": "090c0f1215181b1e2124272a2d303336393c3f4245484b4e5154575a5d606366", + "round": 0, + "signature": "a4fca3d223938deeb9da82d0021a2cfb8cd7463412f7613c8943cd86a4f26b9aa81daf9d218c310ed3bb9e9c6a80b38118d16d27ff981dcc7ca46a65c95be36036eb0e1c4bc31801d2aa5699fc3d9932295f375bec3073e03c4279668429d319" + }, + "quorum_public_key": "99d85ad48c7ca9ffbe49f170e444a77ca98035a446c0fe9919180860757ef0d73e8a04abb18f5b67558baed45e748100", + "block_context": { + "height": 123456, + "round": 0, + "core_chain_locked_height": 2000000, + "time_ms": 1700000000000, + "protocol_version": 12, + "chain_id": "dash-testnet-51" + }, + "expected_valid": false + }, + { + "name": "wrong_app_hash", + "app_hash": "25d905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "envelope": { + "grovedb_proof_hex": "0100da01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a040160002d0401207777777777777777777777777777777777777777777777777777777777777777fd00000002540be400006ab14e39df7d262e55bae17e60bab5d996cb66d6ded567f4d445ba536eef3b931001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010160002f03207777777777777777777777777777777777777777777777777777777777777777000b03fd00000002540be4000000", + "quorum_type": 106, + "quorum_hash": "01080f161d242b323940474e555c636a71787f868d949ba2a9b0b7bec5ccd3da", + "block_id_hash": "090c0f1215181b1e2124272a2d303336393c3f4245484b4e5154575a5d606366", + "round": 0, + "signature": "a4fca3d223938deeb9da82d0021a2cfb8cd7463412f7613c8943cd86a4f26b9aa81daf9d218c310ed3bb9e9c6a80b38118d16d27ff981dcc7ca46a65c95be36036eb0e1c4bc31801d2aa5699fc3d9932295f375bec3073e03c4279668429d319" + }, + "quorum_public_key": "b6087e2054b847ec64a1e18af3930d39fb040b73eb8a10620f9adcb1be85bf7d5ea961dc2458d601ba9003b1e39ead15", + "block_context": { + "height": 123456, + "round": 0, + "core_chain_locked_height": 2000000, + "time_ms": 1700000000000, + "protocol_version": 12, + "chain_id": "dash-testnet-51" + }, + "expected_valid": false + }, + { + "name": "wrong_block_id_hash", + "app_hash": "dad905d8fddd7a31089ed57521ff006ec5946b5648d48056bce493357675ab72", + "envelope": { + "grovedb_proof_hex": "0100da01039118e921c386a7a9c166dbad8a7df9b87fa7f3dc8e3bd4fb42e124cffaa65a02cc9a023abb3efe685ac357116032ee5eb34884d6c3e5861ea7ac662ab511f2371001282192d9f288b3dd8ceeba34fda695ec21d080458ed5d9503eb5916a63494a7a040160002d0401207777777777777777777777777777777777777777777777777777777777777777fd00000002540be400006ab14e39df7d262e55bae17e60bab5d996cb66d6ded567f4d445ba536eef3b931001f61ef78f4632c6b8cb48fd3087644cf1775a597afa62b67660943fccfabd887b1111010160002f03207777777777777777777777777777777777777777777777777777777777777777000b03fd00000002540be4000000", + "quorum_type": 106, + "quorum_hash": "01080f161d242b323940474e555c636a71787f868d949ba2a9b0b7bec5ccd3da", + "block_id_hash": "090c0f1215171b1e2124272a2d303336393c3f4245484b4e5154575a5d606366", + "round": 0, + "signature": "a4fca3d223938deeb9da82d0021a2cfb8cd7463412f7613c8943cd86a4f26b9aa81daf9d218c310ed3bb9e9c6a80b38118d16d27ff981dcc7ca46a65c95be36036eb0e1c4bc31801d2aa5699fc3d9932295f375bec3073e03c4279668429d319" + }, + "quorum_public_key": "b6087e2054b847ec64a1e18af3930d39fb040b73eb8a10620f9adcb1be85bf7d5ea961dc2458d601ba9003b1e39ead15", + "block_context": { + "height": 123456, + "round": 0, + "core_chain_locked_height": 2000000, + "time_ms": 1700000000000, + "protocol_version": 12, + "chain_id": "dash-testnet-51" + }, + "expected_valid": false + } + ] +} diff --git a/packages/rs-platform-cxx/tests/cxx_smoke.cc b/packages/rs-platform-cxx/tests/cxx_smoke.cc new file mode 100644 index 0000000000..4c3b31b146 --- /dev/null +++ b/packages/rs-platform-cxx/tests/cxx_smoke.cc @@ -0,0 +1,13 @@ +#include + +#include + +int main() +{ + try { + platform_ffi::set_context("test", std::uint32_t{0}); + } catch (const rust::Error&) { + return 1; + } + return 0; +} diff --git a/packages/rs-platform-cxx/tests/decoders.rs b/packages/rs-platform-cxx/tests/decoders.rs new file mode 100644 index 0000000000..5061901513 --- /dev/null +++ b/packages/rs-platform-cxx/tests/decoders.rs @@ -0,0 +1,161 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Decoder tests against the rs-dpp-generated vectors in +//! `test_data/dpp_identity_vectors.json` (identities) and the +//! stored-document fixtures in dpp_st_vectors.json (DPNS domain, DashPay +//! profile and contactRequest documents as Drive stores/returns them). + +use dash_platform_cxx::decode; +use serde_json::Value; + +fn hexv(hex: &str) -> Vec { + hex::decode(hex).expect("bad hex in test vector") +} + +fn identity_vectors() -> Value { + serde_json::from_str(include_str!("../test_data/dpp_identity_vectors.json")) + .expect("parse dpp_identity_vectors.json") +} + +fn st_vectors() -> Value { + serde_json::from_str(include_str!("../test_data/dpp_st_vectors.json")) + .expect("parse dpp_st_vectors.json") +} + +#[test] +fn decode_identity_vector() { + let doc = identity_vectors(); + let vector = &doc["identity"]; + let identity = decode::decode_identity(&hexv(vector["serialized_hex"].as_str().unwrap())) + .expect("decode identity"); + + assert_eq!(hex::encode(identity.id), vector["id"].as_str().unwrap()); + assert_eq!(Some(identity.balance), vector["balance"].as_u64()); + assert_eq!(Some(identity.revision), vector["revision"].as_u64()); + + let expected_keys = vector["public_keys"].as_array().unwrap(); + assert_eq!(identity.keys.len(), expected_keys.len()); + for (key, expected) in identity.keys.iter().zip(expected_keys) { + assert_eq!(Some(u64::from(key.id)), expected["id"].as_u64()); + assert_eq!(Some(u64::from(key.purpose)), expected["purpose"].as_u64()); + assert_eq!( + Some(u64::from(key.security_level)), + expected["security_level"].as_u64() + ); + assert_eq!(Some(u64::from(key.key_type)), expected["key_type"].as_u64()); + assert_eq!(Some(key.read_only), expected["read_only"].as_bool()); + assert_eq!(hex::encode(&key.data), expected["data"].as_str().unwrap()); + assert_eq!(key.disabled_at, expected["disabled_at"].as_u64()); + } +} + +#[test] +fn decode_identity_rejects_garbage() { + assert!(decode::decode_identity(&[0xff; 16]).is_err()); +} + +#[test] +fn decode_stored_dpns_domain() { + let doc = st_vectors(); + let vector = &doc["stored_documents"]["domain"]; + let name = decode::decode_dpns_domain(&hexv(vector["serialized_hex"].as_str().unwrap())) + .expect("decode DPNS domain"); + assert_eq!( + hex::encode(name.document_id), + vector["id_hex"].as_str().unwrap() + ); + assert_eq!( + hex::encode(name.owner_id), + vector["owner_id_hex"].as_str().unwrap() + ); + assert_eq!( + hex::encode(name.identity), + vector["identity_id_hex"].as_str().unwrap() + ); + assert_eq!(name.label, vector["label"].as_str().unwrap()); + assert_eq!( + name.normalized_label, + vector["normalized_label"].as_str().unwrap() + ); + assert_eq!(name.parent_domain, "dash"); +} + +#[test] +fn decode_stored_dashpay_profile() { + let doc = st_vectors(); + let vector = &doc["stored_documents"]["profile"]; + let profile = decode::decode_dashpay_profile(&hexv(vector["serialized_hex"].as_str().unwrap())) + .expect("decode DashPay profile"); + assert_eq!( + hex::encode(profile.document_id), + vector["id_hex"].as_str().unwrap() + ); + assert_eq!( + hex::encode(profile.owner_id), + vector["owner_id_hex"].as_str().unwrap() + ); + assert_eq!(Some(profile.revision), vector["revision"].as_u64()); + assert_eq!( + profile.display_name, + vector["display_name"].as_str().unwrap() + ); + assert_eq!( + profile.public_message, + vector["public_message"].as_str().unwrap() + ); + assert_eq!(profile.avatar_url, vector["avatar_url"].as_str().unwrap()); + assert_eq!(Some(profile.created_at), vector["created_at"].as_u64()); + assert_eq!(Some(profile.updated_at), vector["updated_at"].as_u64()); + assert_eq!(profile.avatar_hash, vec![0x88; 32]); + assert_eq!(profile.avatar_fingerprint, vec![0x99; 8]); +} + +#[test] +fn decode_stored_contact_request() { + let doc = st_vectors(); + let vector = &doc["stored_documents"]["contact"]; + let request = decode::decode_contact_request(&hexv(vector["serialized_hex"].as_str().unwrap())) + .expect("decode contact request"); + assert_eq!( + hex::encode(request.document_id), + vector["id_hex"].as_str().unwrap() + ); + assert_eq!( + hex::encode(request.owner_id), + vector["owner_id_hex"].as_str().unwrap() + ); + assert_eq!( + hex::encode(request.to_user_id), + vector["to_user_id_hex"].as_str().unwrap() + ); + assert_eq!( + Some(u64::from(request.sender_key_index)), + vector["sender_key_index"].as_u64() + ); + assert_eq!( + Some(u64::from(request.recipient_key_index)), + vector["recipient_key_index"].as_u64() + ); + assert_eq!( + Some(u64::from(request.account_reference)), + vector["account_reference"].as_u64() + ); + assert_eq!(Some(request.created_at), vector["created_at"].as_u64()); + assert_eq!( + Some(u64::from(request.core_height_created_at)), + vector["core_height_created_at"].as_u64() + ); + assert_eq!(request.encrypted_public_key, vec![0xab; 96]); + assert_eq!(request.encrypted_account_label, vec![0xcd; 48]); +} + +// Documents proven by the drive query vectors are placeholder items, not +// real documents; decoding them must fail cleanly rather than panic. +#[test] +fn decode_rejects_placeholder_documents() { + assert!(decode::decode_dpns_domain(b"proved-dpns-document").is_err()); + assert!(decode::decode_dashpay_profile(b"proved-profile-document").is_err()); + assert!(decode::decode_contact_request(b"proved-contact-document").is_err()); +} diff --git a/packages/rs-platform-cxx/tests/from_proof.rs b/packages/rs-platform-cxx/tests/from_proof.rs new file mode 100644 index 0000000000..23393cde64 --- /dev/null +++ b/packages/rs-platform-cxx/tests/from_proof.rs @@ -0,0 +1,690 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Acceptance tests for the FromProof-driven (request bytes, response bytes) +//! verification seam: synthesize the DAPI protobuf request/response pairs +//! the C++ transport exchanges — grovedb proofs and the quorum signature +//! come from the package fixture corpus — push the +//! fixture quorum key through the provider store, and verify end to end +//! (grovedb replay + Tenderdash BLS quorum signature). +//! +//! Every fixture proof commits to the same root hash and the fixture quorum +//! signed exactly that root, so all positive cases run the full pipeline. +//! The fixture grovedb state stores placeholder payloads at document +//! positions; document queries therefore pin a clean decode failure (the +//! upstream corpus pins the same), while the identity and contested-vote +//! families verify positively. + +use std::sync::Once; + +use dapi_grpc::platform::v0::{self as proto, Proof, ResponseMetadata}; +use prost::Message; +use serde::Deserialize; +use serde_json::Value; + +#[derive(Deserialize)] +struct DriveVectorFile { + identity_id: String, + queries: Vec, +} + +#[derive(Deserialize)] +struct QueryVector { + name: String, + #[serde(default)] + query: Value, + grovedb_proof_hex: String, + expected: Value, +} + +#[derive(Deserialize)] +struct QuorumVectorFile { + vectors: Vec, +} + +#[derive(Deserialize)] +struct QuorumVector { + name: String, + envelope: Envelope, + quorum_public_key: String, + block_context: BlockContext, +} + +#[derive(Deserialize)] +struct Envelope { + quorum_type: u32, + quorum_hash: String, + block_id_hash: String, + round: u32, + signature: String, +} + +#[derive(Deserialize)] +struct BlockContext { + height: u64, + core_chain_locked_height: u32, + time_ms: u64, + protocol_version: u32, + chain_id: String, +} + +fn hexv(hex: &str) -> Vec { + hex::decode(hex).expect("bad hex in test vector") +} + +fn drive_vectors() -> DriveVectorFile { + serde_json::from_str(include_str!("../test_data/drive_query_vectors.json")) + .expect("parse drive_query_vectors.json") +} + +fn quorum_valid() -> QuorumVector { + let file: QuorumVectorFile = + serde_json::from_str(include_str!("../test_data/quorum_sig_vectors.json")) + .expect("parse quorum_sig_vectors.json"); + file.vectors + .into_iter() + .find(|vector| vector.name == "valid") + .expect("valid quorum vector") +} + +fn query<'a>(file: &'a DriveVectorFile, name: &str) -> &'a QueryVector { + file.queries + .iter() + .find(|query| query.name == name) + .unwrap_or_else(|| panic!("query vector not found: {name}")) +} + +/// Installs the fixture context exactly once per test binary: the network +/// the fixture chain id belongs to and the fixture quorum key. Tests that +/// need a failing key lookup tamper the response's quorum hash instead of +/// mutating this shared store. +fn setup() { + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + dash_platform_cxx::provider::set_context("test", 0).expect("set_context"); + let quorum = quorum_valid(); + dash_platform_cxx::provider::update_quorum_keys( + quorum.envelope.quorum_type as u8, + vec![dash_platform_cxx::provider::QuorumKey { + quorum_hash: hexv(&quorum.envelope.quorum_hash) + .try_into() + .expect("32-byte quorum hash"), + public_key: hexv(&quorum.quorum_public_key) + .try_into() + .expect("48-byte quorum key"), + }], + ); + }); +} + +/// The proof envelope binding `grovedb_proof` to the fixture-signed block. +fn proof_msg(grovedb_proof: Vec) -> Proof { + let quorum = quorum_valid(); + Proof { + grovedb_proof, + quorum_hash: hexv(&quorum.envelope.quorum_hash), + signature: hexv(&quorum.envelope.signature), + round: quorum.envelope.round, + block_id_hash: hexv(&quorum.envelope.block_id_hash), + quorum_type: quorum.envelope.quorum_type, + } +} + +fn metadata() -> ResponseMetadata { + let ctx = quorum_valid().block_context; + ResponseMetadata { + height: ctx.height, + core_chain_locked_height: ctx.core_chain_locked_height, + epoch: 0, + time_ms: ctx.time_ms, + protocol_version: ctx.protocol_version, + chain_id: ctx.chain_id, + } +} + +fn check_meta(meta: &dash_platform_cxx::types::Meta) { + let ctx = quorum_valid().block_context; + assert_eq!(meta.height, ctx.height); + assert_eq!(meta.core_chain_locked_height, ctx.core_chain_locked_height); + assert_eq!(meta.time_ms, ctx.time_ms); + assert_eq!(meta.protocol_version, ctx.protocol_version); + assert_eq!(meta.chain_id, ctx.chain_id); +} + +// --- request/response synthesis used by transport adapters + +fn identity_nonce_request(identity_id: Vec) -> Vec { + proto::GetIdentityNonceRequest { + version: Some(proto::get_identity_nonce_request::Version::V0( + proto::get_identity_nonce_request::GetIdentityNonceRequestV0 { + identity_id, + prove: true, + }, + )), + } + .encode_to_vec() +} + +fn identity_nonce_response(proof: Proof) -> Vec { + proto::GetIdentityNonceResponse { + version: Some(proto::get_identity_nonce_response::Version::V0( + proto::get_identity_nonce_response::GetIdentityNonceResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_identity_nonce_response::get_identity_nonce_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +fn identity_contract_nonce_request(identity_id: Vec, contract_id: Vec) -> Vec { + proto::GetIdentityContractNonceRequest { + version: Some(proto::get_identity_contract_nonce_request::Version::V0( + proto::get_identity_contract_nonce_request::GetIdentityContractNonceRequestV0 { + identity_id, + contract_id, + prove: true, + }, + )), + } + .encode_to_vec() +} + +fn identity_contract_nonce_response(proof: Proof) -> Vec { + proto::GetIdentityContractNonceResponse { + version: Some(proto::get_identity_contract_nonce_response::Version::V0( + proto::get_identity_contract_nonce_response::GetIdentityContractNonceResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_identity_contract_nonce_response::get_identity_contract_nonce_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +fn identity_balance_request(identity_id: Vec) -> Vec { + proto::GetIdentityBalanceRequest { + version: Some(proto::get_identity_balance_request::Version::V0( + proto::get_identity_balance_request::GetIdentityBalanceRequestV0 { + id: identity_id, + prove: true, + }, + )), + } + .encode_to_vec() +} + +fn identity_balance_response(proof: Proof) -> Vec { + proto::GetIdentityBalanceResponse { + version: Some(proto::get_identity_balance_response::Version::V0( + proto::get_identity_balance_response::GetIdentityBalanceResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_identity_balance_response::get_identity_balance_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +fn identity_keys_request(identity_id: Vec) -> Vec { + proto::GetIdentityKeysRequest { + version: Some(proto::get_identity_keys_request::Version::V0( + proto::get_identity_keys_request::GetIdentityKeysRequestV0 { + identity_id, + request_type: Some(proto::KeyRequestType { + request: Some(proto::key_request_type::Request::AllKeys(proto::AllKeys {})), + }), + limit: None, + offset: None, + prove: true, + }, + )), + } + .encode_to_vec() +} + +fn identity_keys_response(proof: Proof) -> Vec { + proto::GetIdentityKeysResponse { + version: Some(proto::get_identity_keys_response::Version::V0( + proto::get_identity_keys_response::GetIdentityKeysResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_identity_keys_response::get_identity_keys_response_v0::Result::Proof( + proof, + ), + ), + }, + )), + } + .encode_to_vec() +} + +/// bincode (standard config) encoding of a platform Value::Text, as +/// drive-abci decodes contested index values (discriminant 18 + length + +/// utf8; labels are short so the length is a single byte). +fn bincode_text(text: &str) -> Vec { + let mut out = vec![18u8, u8::try_from(text.len()).expect("short label")]; + out.extend_from_slice(text.as_bytes()); + out +} + +fn contested_request(contract_id: Vec, normalized_label: &str, count: u32) -> Vec { + proto::GetContestedResourceVoteStateRequest { + version: Some(proto::get_contested_resource_vote_state_request::Version::V0( + proto::get_contested_resource_vote_state_request::GetContestedResourceVoteStateRequestV0 { + contract_id, + document_type_name: "domain".to_string(), + index_name: "parentNameAndLabel".to_string(), + index_values: vec![bincode_text("dash"), bincode_text(normalized_label)], + result_type: + proto::get_contested_resource_vote_state_request::get_contested_resource_vote_state_request_v0::ResultType::VoteTally + .into(), + allow_include_locked_and_abstaining_vote_tally: true, + start_at_identifier_info: None, + count: Some(count), + prove: true, + }, + )), + } + .encode_to_vec() +} + +fn contested_response(proof: Proof) -> Vec { + proto::GetContestedResourceVoteStateResponse { + version: Some(proto::get_contested_resource_vote_state_response::Version::V0( + proto::get_contested_resource_vote_state_response::GetContestedResourceVoteStateResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_contested_resource_vote_state_response::get_contested_resource_vote_state_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +/// CBOR where/order-by clauses exactly as the C++ transport encodes them +/// (transport/cbor.h): definite-length arrays of [field, operator, value] +/// triples. +fn dpns_exact_documents_request(contract_id: Vec, normalized_label: &str) -> Vec { + let clauses = vec![ + vec![ + ciborium::Value::Text("normalizedParentDomainName".to_string()), + ciborium::Value::Text("==".to_string()), + ciborium::Value::Text("dash".to_string()), + ], + vec![ + ciborium::Value::Text("normalizedLabel".to_string()), + ciborium::Value::Text("==".to_string()), + ciborium::Value::Text(normalized_label.to_string()), + ], + ]; + let cbor = ciborium::Value::Array( + clauses + .into_iter() + .map(ciborium::Value::Array) + .collect::>(), + ); + let mut where_bytes = Vec::new(); + ciborium::into_writer(&cbor, &mut where_bytes).expect("encode where clauses"); + proto::GetDocumentsRequest { + version: Some(proto::get_documents_request::Version::V0( + proto::get_documents_request::GetDocumentsRequestV0 { + data_contract_id: contract_id, + document_type: "domain".to_string(), + r#where: where_bytes, + order_by: Vec::new(), + limit: 1, + prove: true, + start: None, + }, + )), + } + .encode_to_vec() +} + +fn documents_response(proof: Proof) -> Vec { + proto::GetDocumentsResponse { + version: Some(proto::get_documents_response::Version::V0( + proto::get_documents_response::GetDocumentsResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_documents_response::get_documents_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +const DPNS_CONTRACT_ID_HEX: &str = + "e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c53155"; + +// --- positive cases -------------------------------------------------------- + +#[test] +fn identity_nonce_verifies_end_to_end() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_nonce"); + let request = identity_nonce_request(hexv(&file.identity_id)); + let response = identity_nonce_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (nonce, meta) = dash_platform_cxx::verify::verify_get_identity_nonce(&request, &response) + .expect("identity nonce verifies"); + assert_eq!(nonce, q.expected["nonce"].as_u64()); + check_meta(&meta); +} + +#[test] +fn identity_contract_nonce_verifies_end_to_end() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_contract_nonce"); + let contract_id = hexv(q.query["contract_id"].as_str().expect("contract_id")); + let request = identity_contract_nonce_request(hexv(&file.identity_id), contract_id); + let response = identity_contract_nonce_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (nonce, meta) = + dash_platform_cxx::verify::verify_get_identity_contract_nonce(&request, &response) + .expect("identity contract nonce verifies"); + assert_eq!(nonce, q.expected["nonce"].as_u64()); + check_meta(&meta); +} + +#[test] +fn identity_balance_verifies_end_to_end() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_balance"); + let request = identity_balance_request(hexv(&file.identity_id)); + let response = identity_balance_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (balance, meta) = + dash_platform_cxx::verify::verify_get_identity_balance(&request, &response) + .expect("identity balance verifies"); + assert_eq!(balance, q.expected["balance"].as_u64()); + check_meta(&meta); +} + +#[test] +fn identity_keys_verify_end_to_end() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_keys"); + let request = identity_keys_request(hexv(&file.identity_id)); + let response = identity_keys_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (keys, meta) = dash_platform_cxx::verify::verify_get_identity_keys(&request, &response) + .expect("identity keys verify"); + let keys = keys.expect("identity proven present"); + assert_eq!( + keys.iter().map(|key| key.id).collect::>(), + vec![0, 1, 2] + ); + assert_eq!( + keys.len(), + q.expected["serialized_keys"] + .as_array() + .expect("keys") + .len() + ); + check_meta(&meta); +} + +#[test] +fn contested_vote_state_active_verifies_end_to_end() { + setup(); + let file = drive_vectors(); + let q = query(&file, "contested_vote_state_active"); + let label = q.query["normalized_label"].as_str().expect("label"); + let count = q.query["count"].as_u64().expect("count") as u32; + let request = contested_request(hexv(DPNS_CONTRACT_ID_HEX), label, count); + let response = contested_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (state, meta) = + dash_platform_cxx::verify::verify_get_contested_vote_state(&request, &response) + .expect("contested vote state verifies"); + check_meta(&meta); + assert!(state.contest_found); + assert!(!state.finished); + let expected_contenders = q.expected["contenders"].as_array().unwrap(); + assert_eq!(state.contenders.len(), expected_contenders.len()); + for ((identity, votes), expected) in state.contenders.iter().zip(expected_contenders) { + assert_eq!( + hex::encode(identity), + expected["identity_id"].as_str().unwrap() + ); + assert_eq!(votes.map(u64::from), expected["votes"].as_u64()); + } + assert_eq!( + state.abstain_votes.map(u64::from), + q.expected["abstain_votes"].as_u64() + ); + assert_eq!( + state.lock_votes.map(u64::from), + q.expected["lock_votes"].as_u64() + ); +} + +#[test] +fn contested_vote_state_finished_verifies_end_to_end() { + setup(); + let file = drive_vectors(); + let q = query(&file, "contested_vote_state_finished"); + let label = q.query["normalized_label"].as_str().expect("label"); + let count = q.query["count"].as_u64().expect("count") as u32; + let request = contested_request(hexv(DPNS_CONTRACT_ID_HEX), label, count); + let response = contested_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (state, _) = + dash_platform_cxx::verify::verify_get_contested_vote_state(&request, &response) + .expect("contested vote state verifies"); + assert!(state.contest_found); + assert!(state.finished); + assert!(!state.locked); + assert_eq!( + state.winner.map(hex::encode), + q.expected["winner_identity_id"].as_str().map(String::from) + ); + assert!(state.finished_at_time_ms > 0); +} + +#[test] +fn contested_vote_state_absent_is_proven() { + setup(); + let file = drive_vectors(); + let q = query(&file, "contested_vote_state_absent"); + let label = q.query["normalized_label"].as_str().expect("label"); + let count = q.query["count"].as_u64().expect("count") as u32; + let request = contested_request(hexv(DPNS_CONTRACT_ID_HEX), label, count); + let response = contested_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let (state, _) = + dash_platform_cxx::verify::verify_get_contested_vote_state(&request, &response) + .expect("contested vote state verifies"); + assert!(!state.contest_found); + assert!(state.contenders.is_empty()); +} + +// --- decode-failure pin ---------------------------------------------------- + +/// The fixture grovedb state stores placeholder payloads at document +/// positions: the grovedb + query-shape verification succeeds, and the +/// document decode must fail cleanly (an Err, never a panic). The upstream +/// rs-drive-proof-verifier corpus pins the same behavior. +#[test] +fn placeholder_documents_fail_decoding_cleanly() { + setup(); + let file = drive_vectors(); + let q = query(&file, "dpns_exact"); + let request = dpns_exact_documents_request(hexv(DPNS_CONTRACT_ID_HEX), "alice"); + let response = documents_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let err = dash_platform_cxx::verify::verify_get_documents(&request, &response) + .expect_err("placeholder documents must not decode"); + // The failure must come from the DPP decode, not from the grovedb + // replay: a query-shape drift would surface as a "grovedb:" proof error. + assert!( + !err.contains("grovedb"), + "unexpected proof-layer error: {err}" + ); +} + +// --- negative cases (quorum binding) --------------------------------------- + +#[test] +fn tampered_signature_is_rejected() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_nonce"); + let mut proof = proof_msg(hexv(&q.grovedb_proof_hex)); + proof.signature[10] ^= 0x01; + let request = identity_nonce_request(hexv(&file.identity_id)); + let response = identity_nonce_response(proof); + dash_platform_cxx::verify::verify_get_identity_nonce(&request, &response) + .expect_err("tampered signature must fail"); +} + +#[test] +fn unknown_quorum_is_rejected() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_nonce"); + let mut proof = proof_msg(hexv(&q.grovedb_proof_hex)); + proof.quorum_hash[0] ^= 0x01; + let request = identity_nonce_request(hexv(&file.identity_id)); + let response = identity_nonce_response(proof); + let err = dash_platform_cxx::verify::verify_get_identity_nonce(&request, &response) + .expect_err("unknown quorum must fail"); + assert!(err.contains("quorum"), "unexpected error: {err}"); +} + +#[test] +fn tampered_grovedb_proof_is_rejected() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_nonce"); + let mut grovedb_proof = hexv(&q.grovedb_proof_hex); + let mid = grovedb_proof.len() / 2; + grovedb_proof[mid] ^= 0x01; + let request = identity_nonce_request(hexv(&file.identity_id)); + let response = identity_nonce_response(proof_msg(grovedb_proof)); + dash_platform_cxx::verify::verify_get_identity_nonce(&request, &response) + .expect_err("tampered grovedb proof must fail"); +} + +/// The signed metadata is part of the quorum-signature preimage: a replayed +/// proof with altered height must fail even though the signature itself is +/// valid for the original block. +#[test] +fn tampered_metadata_height_is_rejected() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_nonce"); + let request = identity_nonce_request(hexv(&file.identity_id)); + let mut mtd = metadata(); + mtd.height += 1; + let response = proto::GetIdentityNonceResponse { + version: Some(proto::get_identity_nonce_response::Version::V0( + proto::get_identity_nonce_response::GetIdentityNonceResponseV0 { + metadata: Some(mtd), + result: Some( + proto::get_identity_nonce_response::get_identity_nonce_response_v0::Result::Proof( + proof_msg(hexv(&q.grovedb_proof_hex)), + ), + ), + }, + )), + } + .encode_to_vec(); + dash_platform_cxx::verify::verify_get_identity_nonce(&request, &response) + .expect_err("tampered signed height must fail"); +} + +fn identity_request(id: Vec) -> Vec { + proto::GetIdentityRequest { + version: Some(proto::get_identity_request::Version::V0( + proto::get_identity_request::GetIdentityRequestV0 { id, prove: true }, + )), + } + .encode_to_vec() +} + +fn identity_response(proof: Proof) -> Vec { + proto::GetIdentityResponse { + version: Some(proto::get_identity_response::Version::V0( + proto::get_identity_response::GetIdentityResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_identity_response::get_identity_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +fn identity_by_pubkey_hash_request(public_key_hash: Vec) -> Vec { + proto::GetIdentityByPublicKeyHashRequest { + version: Some(proto::get_identity_by_public_key_hash_request::Version::V0( + proto::get_identity_by_public_key_hash_request::GetIdentityByPublicKeyHashRequestV0 { + public_key_hash, + prove: true, + }, + )), + } + .encode_to_vec() +} + +fn identity_by_pubkey_hash_response(proof: Proof) -> Vec { + proto::GetIdentityByPublicKeyHashResponse { + version: Some(proto::get_identity_by_public_key_hash_response::Version::V0( + proto::get_identity_by_public_key_hash_response::GetIdentityByPublicKeyHashResponseV0 { + metadata: Some(metadata()), + result: Some( + proto::get_identity_by_public_key_hash_response::get_identity_by_public_key_hash_response_v0::Result::Proof(proof), + ), + }, + )), + } + .encode_to_vec() +} + +/// The fixture corpus has no full-identity proof: the pubkey-hash vectors +/// prove only the unique-hash -> identity-id mapping, while the verifier +/// requires the full identity subtree (upstream skips these vectors for the +/// same reason). Until a full-identity fixture is regenerated, the two +/// flagship identity paths are pinned negatively: an id-mapping-only proof +/// must be rejected cleanly, never verified and never a panic. +#[test] +fn identity_by_pubkey_hash_rejects_id_mapping_only_proof() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_by_public_key_hash_present"); + let request = + identity_by_pubkey_hash_request(hexv(q.query["public_key_hash"].as_str().unwrap())); + let response = identity_by_pubkey_hash_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let err = dash_platform_cxx::verify::verify_get_identity_by_pubkey_hash(&request, &response) + .expect_err("id-mapping-only proof must not satisfy full-identity verification"); + assert!( + err.contains("identity-by-public-key-hash proof verification failed"), + "unexpected error: {err}" + ); +} + +/// A structurally valid proof for a different query shape must fail cleanly +/// when presented as a full-identity proof. +#[test] +fn identity_rejects_wrong_shape_proof() { + setup(); + let file = drive_vectors(); + let q = query(&file, "identity_nonce"); + let request = identity_request(hexv(&file.identity_id)); + let response = identity_response(proof_msg(hexv(&q.grovedb_proof_hex))); + let err = dash_platform_cxx::verify::verify_get_identity(&request, &response) + .expect_err("nonce-shaped proof must not satisfy full-identity verification"); + assert!( + err.contains("identity proof verification failed"), + "unexpected error: {err}" + ); +} diff --git a/packages/rs-platform-cxx/tests/signing.rs b/packages/rs-platform-cxx/tests/signing.rs new file mode 100644 index 0000000000..4a6043b1dc --- /dev/null +++ b/packages/rs-platform-cxx/tests/signing.rs @@ -0,0 +1,407 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! State-transition builder tests against the rs-dpp-generated fixtures in +//! `test_data/dpp_st_vectors.json` (Platform v4.0.0, protocol +//! version 12). The vectors carry full serialized transitions built from +//! deterministic inputs (fixed keys, entropy, RFC6979 ECDSA), so the +//! builders are checked byte-for-byte. +//! +//! The signing callback stands in for the C++ WalletSigner: it receives the +//! key id plus the double-SHA256 digest of the signable bytes and answers +//! with `dashcore::signer::sign_hash`, which is exactly what +//! `signer::sign(signable_bytes, key)` would produce. + +use std::sync::Mutex; + +use dash_platform_cxx::st::{self, AssetLockProofInput, NewIdentityKey, ASSET_LOCK_KEY_ID}; +use dash_platform_cxx::types::KeyInfo; +use dpp::dashcore::hashes::{sha256, Hash}; +use dpp::dashcore::signer as dash_signer; +use serde_json::Value; + +fn hexv(hex: &str) -> Vec { + hex::decode(hex).expect("bad hex in test vector") +} + +fn hex32(hex: &str) -> [u8; 32] { + hexv(hex).try_into().expect("expected 32 bytes") +} + +fn vectors() -> Value { + let doc: Value = serde_json::from_str(include_str!("../test_data/dpp_st_vectors.json")) + .expect("parse dpp_st_vectors.json"); + assert_eq!(doc["platform_repo_tag"].as_str(), Some("v4.0.0")); + doc +} + +/// Test signer: routes key ids to the fixed vector keys and records every +/// digest it was asked to sign. +struct TestSigner { + master_sk: [u8; 32], + high_sk: [u8; 32], + asset_lock_sk: [u8; 32], + digests: Mutex>, +} + +impl TestSigner { + fn from_vectors(doc: &Value) -> Self { + TestSigner { + master_sk: hex32(doc["keys"]["master"]["private_key_hex"].as_str().unwrap()), + high_sk: hex32(doc["keys"]["high"]["private_key_hex"].as_str().unwrap()), + asset_lock_sk: hex32( + doc["keys"]["asset_lock"]["private_key_hex"] + .as_str() + .unwrap(), + ), + digests: Mutex::new(Vec::new()), + } + } + + fn sign(&self, key_id: u32, digest: [u8; 32]) -> Option> { + self.digests.lock().unwrap().push((key_id, digest)); + let sk = match key_id { + 0 => self.master_sk, + 1 => self.high_sk, + ASSET_LOCK_KEY_ID => self.asset_lock_sk, + _ => return None, + }; + dash_signer::sign_hash(&digest, &sk) + .ok() + .map(|s| s.to_vec()) + } +} + +fn high_key(doc: &Value) -> KeyInfo { + KeyInfo { + id: 1, + purpose: 0, // AUTHENTICATION + security_level: 2, // HIGH + key_type: 0, // ECDSA_SECP256K1 + read_only: false, + data: hexv(doc["keys"]["high"]["public_key_hex"].as_str().unwrap()), + disabled_at: None, + } +} + +fn check_built( + built: &dash_platform_cxx::types::BuiltTransition, + vector: &Value, + signer: &TestSigner, + expected_key_id: u32, +) { + assert_eq!( + hex::encode(&built.bytes), + vector["serialized_hex"].as_str().unwrap(), + "serialized transition bytes" + ); + assert_eq!( + built.hash, + sha256::Hash::hash(&built.bytes).to_byte_array(), + "hash must be single sha256 of the bytes" + ); + let expected_digest = hex32(vector["digest_hex"].as_str().unwrap()); + let digests = signer.digests.lock().unwrap(); + assert!( + digests.contains(&(expected_key_id, expected_digest)), + "signer must be asked for the vector digest with key id {expected_key_id}; got {digests:?}" + ); +} + +#[test] +fn dpns_preorder() { + let doc = vectors(); + let vector = &doc["dpns_preorder"]; + let signer = TestSigner::from_vectors(&doc); + let built = st::build_dpns_preorder( + &hexv(vector["owner_id_hex"].as_str().unwrap()), + vector["identity_contract_nonce"].as_u64().unwrap(), + vector["label"].as_str().unwrap(), + &hexv(vector["salt_hex"].as_str().unwrap()), + &high_key(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build DPNS preorder"); + check_built(&built, vector, &signer, 1); +} + +fn build_domain( + doc: &Value, + vector: &Value, + signer: &TestSigner, +) -> dash_platform_cxx::types::BuiltTransition { + st::build_dpns_domain( + &hexv(vector["owner_id_hex"].as_str().unwrap()), + vector["identity_contract_nonce"].as_u64().unwrap(), + vector["label"].as_str().unwrap(), + vector["normalized_label"].as_str().unwrap(), + "dash", + &hexv(vector["salt_hex"].as_str().unwrap()), + &high_key(doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build DPNS domain") +} + +#[test] +fn dpns_domain() { + let doc = vectors(); + let vector = &doc["dpns_domain"]; + assert_eq!(vector["contested"].as_bool(), Some(false)); + let signer = TestSigner::from_vectors(&doc); + let built = build_domain(&doc, vector, &signer); + check_built(&built, vector, &signer, 1); +} + +// A contested label must automatically attach the prefunded voting balance +// (rs-dpp computes it from the contested unique index of the domain type). +#[test] +fn dpns_domain_contested() { + let doc = vectors(); + let vector = &doc["dpns_domain_contested"]; + assert_eq!(vector["contested"].as_bool(), Some(true)); + let signer = TestSigner::from_vectors(&doc); + let built = build_domain(&doc, vector, &signer); + check_built(&built, vector, &signer, 1); +} + +#[test] +fn dpns_domain_rejects_bad_normalization() { + let doc = vectors(); + let signer = TestSigner::from_vectors(&doc); + let err = st::build_dpns_domain( + &[0x11; 32], + 1, + "Alice", + "alice", // wrong: o->0, l/i->1 normalization gives "a11ce" + "dash", + &[0x55; 32], + &high_key(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .unwrap_err(); + assert!(err.contains("normalized label"), "{err}"); +} + +#[test] +fn dashpay_profile_create() { + let doc = vectors(); + let vector = &doc["dashpay_profile_create"]; + let signer = TestSigner::from_vectors(&doc); + let built = st::build_profile( + &hexv(vector["owner_id_hex"].as_str().unwrap()), + vector["identity_contract_nonce"].as_u64().unwrap(), + vector["display_name"].as_str().unwrap(), + vector["public_message"].as_str().unwrap(), + vector["avatar_url"].as_str().unwrap(), + &hexv(vector["avatar_hash_hex"].as_str().unwrap()), + &hexv(vector["avatar_fingerprint_hex"].as_str().unwrap()), + 1, + None, + &hexv(vector["entropy_hex"].as_str().unwrap()), + &high_key(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build profile create"); + check_built(&built, vector, &signer, 1); +} + +#[test] +fn dashpay_profile_replace() { + let doc = vectors(); + let create = &doc["dashpay_profile_create"]; + let vector = &doc["dashpay_profile_replace"]; + let signer = TestSigner::from_vectors(&doc); + // The replace fixture carries the create fixture's profile fields with + // an updated publicMessage (see the serialized property map). + let built = st::build_profile( + &hexv(vector["owner_id_hex"].as_str().unwrap()), + vector["identity_contract_nonce"].as_u64().unwrap(), + create["display_name"].as_str().unwrap(), + vector["public_message"].as_str().unwrap(), + create["avatar_url"].as_str().unwrap(), + &hexv(create["avatar_hash_hex"].as_str().unwrap()), + &hexv(create["avatar_fingerprint_hex"].as_str().unwrap()), + vector["revision"].as_u64().unwrap(), + Some(&hexv(vector["document_id_hex"].as_str().unwrap())), + &[0u8; 32], // entropy is unused for replacements + &high_key(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build profile replace"); + check_built(&built, vector, &signer, 1); +} + +#[test] +fn dashpay_contact_request() { + let doc = vectors(); + let vector = &doc["dashpay_contact_request"]; + let signer = TestSigner::from_vectors(&doc); + let built = st::build_contact_request( + &hexv(vector["owner_id_hex"].as_str().unwrap()), + vector["identity_contract_nonce"].as_u64().unwrap(), + &hexv(vector["to_user_id_hex"].as_str().unwrap()), + &hexv(vector["encrypted_public_key_hex"].as_str().unwrap()), + vector["sender_key_index"].as_u64().unwrap() as u32, + vector["recipient_key_index"].as_u64().unwrap() as u32, + vector["account_reference"].as_u64().unwrap() as u32, + &hexv(vector["encrypted_account_label_hex"].as_str().unwrap()), + &hexv(vector["entropy_hex"].as_str().unwrap()), + &high_key(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build contact request"); + check_built(&built, vector, &signer, 1); +} + +fn identity_keys(doc: &Value) -> Vec { + vec![ + NewIdentityKey { + id: 0, + purpose: 0, // AUTHENTICATION + security_level: 0, // MASTER + pubkey: hexv(doc["keys"]["master"]["public_key_hex"].as_str().unwrap()), + }, + NewIdentityKey { + id: 1, + purpose: 0, + security_level: 2, // HIGH + pubkey: hexv(doc["keys"]["high"]["public_key_hex"].as_str().unwrap()), + }, + ] +} + +fn check_identity_create( + built: &dash_platform_cxx::types::BuiltTransition, + vector: &Value, + signer: &TestSigner, +) { + assert_eq!( + hex::encode(&built.bytes), + vector["serialized_hex"].as_str().unwrap(), + "serialized identity create bytes" + ); + let expected_digest = hex32(vector["digest_hex"].as_str().unwrap()); + let digests = signer.digests.lock().unwrap().clone(); + // Every identity key and the asset-lock key sign the same digest. + for key_id in [0, 1, ASSET_LOCK_KEY_ID] { + assert!( + digests.contains(&(key_id, expected_digest)), + "key {key_id} must sign the vector digest" + ); + } + + // Round trip: the built bytes must deserialize back into an + // IdentityCreate transition with signatures present. + use dpp::serialization::PlatformDeserializable; + use dpp::state_transition::StateTransition; + let deserialized = + StateTransition::deserialize_from_bytes(&built.bytes).expect("round-trip deserialize"); + match &deserialized { + StateTransition::IdentityCreate(_) => {} + other => panic!("expected IdentityCreate, got {other:?}"), + } + let reserialized = { + use dpp::serialization::PlatformSerializable; + deserialized.serialize_to_bytes().expect("re-serialize") + }; + assert_eq!(reserialized, built.bytes, "round trip must be stable"); + assert!( + deserialized + .signature() + .map(|signature| !signature.is_empty()) + .unwrap_or(false), + "outer signature must be present" + ); +} + +#[test] +fn identity_create_instant() { + let doc = vectors(); + let vector = &doc["identity_create_instant"]; + let signer = TestSigner::from_vectors(&doc); + let built = st::build_identity_create( + AssetLockProofInput::Instant { + transaction: hexv(vector["transaction_hex"].as_str().unwrap()), + instant_lock: hexv(vector["instant_lock_hex"].as_str().unwrap()), + output_index: vector["output_index"].as_u64().unwrap() as u32, + }, + &identity_keys(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build identity create (instant)"); + check_identity_create(&built, vector, &signer); +} + +#[test] +fn identity_create_chain() { + let doc = vectors(); + let vector = &doc["identity_create_chain"]; + let signer = TestSigner::from_vectors(&doc); + let built = st::build_identity_create( + AssetLockProofInput::Chain { + core_chain_locked_height: vector["core_chain_locked_height"].as_u64().unwrap() as u32, + out_point: hexv(vector["out_point_hex"].as_str().unwrap()) + .try_into() + .expect("36-byte outpoint"), + }, + &identity_keys(&doc), + &|key_id, digest| signer.sign(key_id, digest), + ) + .expect("build identity create (chain)"); + check_identity_create(&built, vector, &signer); +} + +// Keys must arrive at the wire sorted by id with no duplicates, regardless +// of input order. +#[test] +fn identity_create_sorts_and_rejects_duplicate_keys() { + let doc = vectors(); + let vector = &doc["identity_create_chain"]; + let signer = TestSigner::from_vectors(&doc); + let proof = || AssetLockProofInput::Chain { + core_chain_locked_height: vector["core_chain_locked_height"].as_u64().unwrap() as u32, + out_point: hexv(vector["out_point_hex"].as_str().unwrap()) + .try_into() + .unwrap(), + }; + + let mut reversed = identity_keys(&doc); + reversed.reverse(); + let built = st::build_identity_create(proof(), &reversed, &|key_id, digest| { + signer.sign(key_id, digest) + }) + .expect("reversed key order still builds"); + assert_eq!( + hex::encode(&built.bytes), + vector["serialized_hex"].as_str().unwrap() + ); + + let mut duplicated = identity_keys(&doc); + duplicated[1].id = 0; + let err = st::build_identity_create(proof(), &duplicated, &|key_id, digest| { + signer.sign(key_id, digest) + }) + .unwrap_err(); + assert!(err.contains("duplicate"), "{err}"); +} + +#[test] +fn signer_failure_is_reported() { + let doc = vectors(); + let vector = &doc["dpns_preorder"]; + let err = st::build_dpns_preorder( + &hexv(vector["owner_id_hex"].as_str().unwrap()), + 2, + vector["label"].as_str().unwrap(), + &hexv(vector["salt_hex"].as_str().unwrap()), + &high_key(&doc), + &|_key_id, _digest| None, // wallet refuses + ) + .unwrap_err(); + assert!( + err.contains("signing failed") || err.contains("locked"), + "{err}" + ); +} diff --git a/packages/rs-sdk/src/error.rs b/packages/rs-sdk/src/error.rs index cc8309ebcd..89ade69e74 100644 --- a/packages/rs-sdk/src/error.rs +++ b/packages/rs-sdk/src/error.rs @@ -137,6 +137,9 @@ impl From for Error { fn from(value: dash_platform_queries::Error) -> Self { match value { dash_platform_queries::Error::Config(msg) => Self::Config(msg), + // Builder input validation moved to the query core keeps surfacing + // as Generic with the exact messages it produced inside this crate. + dash_platform_queries::Error::InvalidInput(msg) => Self::Generic(msg), dash_platform_queries::Error::Drive(e) => Self::Drive(e), dash_platform_queries::Error::Protocol(e) => Self::Protocol(e), } diff --git a/packages/rs-sdk/src/platform/dashpay/contact_request.rs b/packages/rs-sdk/src/platform/dashpay/contact_request.rs index d595faaaed..fd2aaa395c 100644 --- a/packages/rs-sdk/src/platform/dashpay/contact_request.rs +++ b/packages/rs-sdk/src/platform/dashpay/contact_request.rs @@ -5,11 +5,13 @@ use crate::platform::transition::put_document::PutDocument; use crate::platform::Document; use crate::{Error, Sdk}; +use dash_platform_queries::dashpay::{ + build_contact_request_document, validate_auto_accept_proof, ContactRequestDocumentParams, +}; use dpp::dashcore::secp256k1::rand::rngs::StdRng; use dpp::dashcore::secp256k1::rand::{RngCore, SeedableRng}; use dpp::dashcore::secp256k1::{PublicKey, SecretKey}; use dpp::data_contract::accessors::v0::DataContractV0Getters; -use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::document::DocumentV0; use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -259,14 +261,11 @@ impl Sdk { H: FnOnce(u32) -> Hut, Hut: std::future::Future, Error>>, { - // Validate auto accept proof size if provided + // Validate auto accept proof size if provided. The shared builder + // validates again, but checking here first keeps the failure local — + // before the recipient fetch and ECDH work below. if let Some(ref proof) = input.auto_accept_proof { - if proof.len() < 38 || proof.len() > 102 { - return Err(Error::Generic(format!( - "autoAcceptProof must be 38-102 bytes, got {}", - proof.len() - ))); - } + validate_auto_accept_proof(proof)?; } // Fetch recipient identity if only ID was provided @@ -362,90 +361,45 @@ impl Sdk { let mut xpub_iv = [0u8; 16]; rng.fill_bytes(&mut xpub_iv); - // Encrypt the extended public key (includes IV prepended) + // Encrypt the extended public key (includes IV prepended). The shared + // builder rejects any ciphertext that isn't exactly 96 bytes + // (16-byte IV + 80-byte encrypted data). let encrypted_public_key = encrypt_extended_public_key(&shared_key, &xpub_iv, &extended_public_key); - // Validate encrypted public key size (must be exactly 96 bytes: 16-byte IV + 80-byte encrypted data) - if encrypted_public_key.len() != 96 { - return Err(Error::Generic(format!( - "Encrypted public key size mismatch: expected 96 bytes, got {}", - encrypted_public_key.len() - ))); - } - - // Encrypt the account label if provided (includes IV prepended) - let encrypted_account_label = if let Some(ref label) = input.account_label { + // Encrypt the account label if provided (includes IV prepended). The + // shared builder rejects any ciphertext outside 48-80 bytes + // (16-byte IV + 32-64 byte encrypted data). + let encrypted_account_label = input.account_label.as_ref().map(|label| { let mut label_iv = [0u8; 16]; rng.fill_bytes(&mut label_iv); - let encrypted = encrypt_account_label(&shared_key, &label_iv, label); - - // Validate encrypted label size (48-80 bytes: 16-byte IV + 32-64 byte encrypted data) - if encrypted.len() < 48 || encrypted.len() > 80 { - return Err(Error::Generic(format!( - "Encrypted account label size out of range: expected 48-80 bytes, got {}", - encrypted.len() - ))); - } - Some(encrypted) - } else { - None - }; + encrypt_account_label(&shared_key, &label_iv, label) + }); // Fetch DashPay contract let dashpay_contract = self.fetch_dashpay_contract().await?; - // Get contactRequest document type - let contact_request_document_type = dashpay_contract - .document_type_for_name("contactRequest") - .map_err(|_| { - Error::Generic("DashPay contactRequest document type not found".to_string()) - })?; - // Generate entropy for document ID let mut rng = StdRng::from_entropy(); let entropy = Bytes32::random_with_rng(&mut rng); - // Generate document ID + // Assemble the document in the shared transport-free builder, so + // networked and embedder flows produce byte-identical documents. let sender_id = input.sender_identity.id().to_owned(); - let document_id = Document::generate_document_id_v0( - &dashpay_contract.id(), - &sender_id, - contact_request_document_type.name(), - entropy.as_slice(), - ); - - // Build document properties - let mut properties = BTreeMap::new(); - let recipient_id = recipient_identity.id().to_owned(); - properties.insert( - "toUserId".to_string(), - Value::Identifier(recipient_id.to_buffer()), - ); - properties.insert( - "encryptedPublicKey".to_string(), - Value::Bytes(encrypted_public_key), - ); - properties.insert( - "senderKeyIndex".to_string(), - Value::U32(input.sender_key_index), - ); - properties.insert( - "recipientKeyIndex".to_string(), - Value::U32(input.recipient_key_index), - ); - properties.insert( - "accountReference".to_string(), - Value::U32(input.account_reference), - ); - - // Add optional fields - if let Some(label) = encrypted_account_label { - properties.insert("encryptedAccountLabel".to_string(), Value::Bytes(label)); - } - if let Some(proof) = input.auto_accept_proof { - properties.insert("autoAcceptProof".to_string(), Value::Bytes(proof)); - } + let (document_id, properties) = build_contact_request_document( + &dashpay_contract, + ContactRequestDocumentParams { + sender_id, + recipient_id: recipient_identity.id().to_owned(), + sender_key_index: input.sender_key_index, + recipient_key_index: input.recipient_key_index, + account_reference: input.account_reference, + encrypted_public_key, + encrypted_account_label, + auto_accept_proof: input.auto_accept_proof, + entropy: entropy.0, + }, + )?; // Return the essential fields for the contact request, including the // entropy that derived `document_id` so the broadcast path can reuse it. diff --git a/packages/rs-sdk/src/platform/dashpay/mod.rs b/packages/rs-sdk/src/platform/dashpay/mod.rs index 182edd8854..a9e53b8778 100644 --- a/packages/rs-sdk/src/platform/dashpay/mod.rs +++ b/packages/rs-sdk/src/platform/dashpay/mod.rs @@ -12,6 +12,9 @@ pub use contact_request::{ EcdhProvider, RecipientIdentity, SendContactRequestInput, SendContactRequestResult, }; pub use contact_request_queries::ContactRequestDocuments; +pub use dash_platform_queries::dashpay::{ + build_contact_request_document, validate_auto_accept_proof, ContactRequestDocumentParams, +}; use crate::platform::Fetch; use crate::{Error, Sdk}; diff --git a/packages/rs-sdk/src/platform/dpns_usernames/mod.rs b/packages/rs-sdk/src/platform/dpns_usernames/mod.rs index 6de3e2950d..15021d9042 100644 --- a/packages/rs-sdk/src/platform/dpns_usernames/mod.rs +++ b/packages/rs-sdk/src/platform/dpns_usernames/mod.rs @@ -3,7 +3,8 @@ mod queries; pub use contested_queries::ContestedDpnsUsername; pub use dash_platform_queries::dpns_usernames::{ - convert_to_homograph_safe_chars, is_contested_username, is_valid_username, + build_dpns_preorder_and_domain_documents, convert_to_homograph_safe_chars, + is_contested_username, is_valid_username, }; pub use queries::DpnsUsername; @@ -14,14 +15,12 @@ use dash_context_provider::ContextProvider; use dpp::dashcore::secp256k1::rand::rngs::StdRng; use dpp::dashcore::secp256k1::rand::{Rng, SeedableRng}; use dpp::data_contract::accessors::v0::DataContractV0Getters; -use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; -use dpp::document::{DocumentV0, DocumentV0Getters}; +use dpp::document::DocumentV0Getters; use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::signer::Signer; use dpp::identity::{Identity, IdentityPublicKey}; use dpp::platform_value::{Bytes32, Value}; use dpp::prelude::Identifier; -use std::collections::BTreeMap; use std::sync::Arc; fn extract_dpns_label(name: &str) -> &str { @@ -45,14 +44,6 @@ fn normalize_dpns_label(input: &str) -> String { convert_to_homograph_safe_chars(extract_dpns_label(input)) } -/// Hash a buffer twice using SHA256 (double SHA256) -fn hash_double(data: Vec) -> [u8; 32] { - use dpp::dashcore::hashes::{sha256d, Hash}; - // sha256d already does double SHA256 - let hash = sha256d::Hash::hash(&data); - hash.to_byte_array() -} - /// Callback type for preorder document pub type PreorderCallback = Box; @@ -164,95 +155,17 @@ impl Sdk { let entropy = Bytes32::random_with_rng(&mut rng); let salt: [u8; 32] = rng.gen(); - // Generate document IDs - let identity_id = input.identity.id().to_owned(); - let preorder_id = Document::generate_document_id_v0( - &dpns_contract.id(), - &identity_id, - preorder_document_type.name(), - entropy.as_slice(), - ); - let domain_id = Document::generate_document_id_v0( - &dpns_contract.id(), - &identity_id, - domain_document_type.name(), - entropy.as_slice(), - ); - - // Create salted domain hash for preorder + // Assemble both documents in the shared transport-free builder, so + // networked and embedder flows produce byte-identical documents. + let (preorder_document, domain_document) = build_dpns_preorder_and_domain_documents( + &dpns_contract, + input.identity.id().to_owned(), + &input.label, + entropy.0, + salt, + )?; + let normalized_label = convert_to_homograph_safe_chars(&input.label); - let mut salted_domain_buffer: Vec = vec![]; - salted_domain_buffer.extend(salt); - salted_domain_buffer.extend((normalized_label.clone() + ".dash").as_bytes()); - let salted_domain_hash = hash_double(salted_domain_buffer); - - // Create preorder document - let preorder_document = Document::V0(DocumentV0 { - id: preorder_id, - owner_id: identity_id, - properties: BTreeMap::from([( - "saltedDomainHash".to_string(), - Value::Bytes32(salted_domain_hash), - )]), - revision: None, - created_at: None, - updated_at: None, - transferred_at: None, - created_at_block_height: None, - updated_at_block_height: None, - transferred_at_block_height: None, - created_at_core_block_height: None, - updated_at_core_block_height: None, - transferred_at_core_block_height: None, - creator_id: None, - }); - - // Create domain document - let domain_document = Document::V0(DocumentV0 { - id: domain_id, - owner_id: identity_id, - properties: BTreeMap::from([ - ( - "parentDomainName".to_string(), - Value::Text("dash".to_string()), - ), - ( - "normalizedParentDomainName".to_string(), - Value::Text("dash".to_string()), - ), - ("label".to_string(), Value::Text(input.label.clone())), - ( - "normalizedLabel".to_string(), - Value::Text(normalized_label.clone()), - ), - ("preorderSalt".to_string(), Value::Bytes32(salt)), - ( - "records".to_string(), - Value::Map(vec![( - Value::Text("identity".to_string()), - Value::Identifier(identity_id.to_buffer()), - )]), - ), - ( - "subdomainRules".to_string(), - Value::Map(vec![( - Value::Text("allowSubdomains".to_string()), - Value::Bool(false), - )]), - ), - ]), - revision: None, - created_at: None, - updated_at: None, - transferred_at: None, - created_at_block_height: None, - updated_at_block_height: None, - transferred_at_block_height: None, - created_at_core_block_height: None, - updated_at_core_block_height: None, - transferred_at_core_block_height: None, - creator_id: None, - }); // Submit preorder document first let platform_preorder_document = preorder_document diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index fa85a30a0d..75503ba142 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -3,15 +3,18 @@ use super::validation::ensure_valid_state_transition_structure; use super::waitable::Waitable; use crate::platform::transition::put_settings::PutSettings; use crate::{Error, Sdk}; +// Transport-free helpers shared with embedders; the implementations moved to +// `dash-platform-queries`. +pub use dash_platform_queries::transition::put_document::{ + ensure_entropy_matches_document_id, prepare_document_for_transition, +}; use dpp::dashcore::secp256k1::rand::rngs::StdRng; use dpp::dashcore::secp256k1::rand::{Rng, SeedableRng}; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; -use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::data_contract::document_type::DocumentType; use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters, INITIAL_REVISION}; use dpp::identity::signer::Signer; use dpp::identity::IdentityPublicKey; -use dpp::prelude::Identifier; use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use dpp::state_transition::batch_transition::BatchTransition; use dpp::state_transition::StateTransition; @@ -162,162 +165,3 @@ impl> PutDocument for Document { Self::wait_for_response(sdk, state_transition, settings).await } } - -fn prepare_document_for_transition(document: &Document, document_type: &DocumentType) -> Document { - let mut document = document.clone(); - document_type - .as_ref() - .sanitize_document_properties(document.properties_mut()); - document -} - -/// Ensures a caller-supplied `entropy` derives the same document id already set -/// on a create document. -/// -/// A document-create state transition carries both the document id and the -/// entropy, and Drive recomputes the id from the entropy during -/// `advanced_structure` validation, rejecting the transition with -/// `InvalidDocumentTransitionIdError` when they disagree. Because -/// [`PutDocument::put_to_platform`] trusts the caller's id verbatim in the -/// `Some(entropy)` arm, a two-phase caller whose id and entropy have drifted -/// would only discover the mismatch after paying (a bumped identity-contract -/// nonce). This check surfaces the mismatch locally before broadcasting. -fn ensure_entropy_matches_document_id( - contract_id: &Identifier, - owner_id: &Identifier, - document_type_name: &str, - entropy: &[u8; 32], - document_id: Identifier, -) -> Result<(), Error> { - let expected_id = Document::generate_document_id_v0( - contract_id, - owner_id, - document_type_name, - entropy.as_slice(), - ); - if expected_id != document_id { - return Err(Error::Generic(format!( - "document id {document_id} does not match the id {expected_id} derived from the \ - supplied entropy; the entropy must be the one used to generate the document id" - ))); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use dpp::data_contract::config::DataContractConfig; - use dpp::document::DocumentV0; - use dpp::platform_value::{platform_value, Value}; - use dpp::version::PlatformVersion; - use std::collections::BTreeMap; - - fn contract_id() -> Identifier { - Identifier::from([1u8; 32]) - } - - fn owner_id() -> Identifier { - Identifier::from([2u8; 32]) - } - - #[test] - fn matching_entropy_and_id_pass() { - let entropy = [7u8; 32]; - let id = Document::generate_document_id_v0( - &contract_id(), - &owner_id(), - "contactRequest", - entropy.as_slice(), - ); - - ensure_entropy_matches_document_id( - &contract_id(), - &owner_id(), - "contactRequest", - &entropy, - id, - ) - .expect("id derived from the supplied entropy must be accepted"); - } - - #[test] - fn mismatched_entropy_and_id_error_before_broadcast() { - // The id was derived from E1, but the caller passes E2 != E1 (mirroring - // the very drift consensus rejects with InvalidDocumentTransitionIdError). - let entropy_used = [1u8; 32]; - let id = Document::generate_document_id_v0( - &contract_id(), - &owner_id(), - "contactRequest", - entropy_used.as_slice(), - ); - - let different_entropy = [2u8; 32]; - let result = ensure_entropy_matches_document_id( - &contract_id(), - &owner_id(), - "contactRequest", - &different_entropy, - id, - ); - - assert!( - matches!(result, Err(Error::Generic(_))), - "a document id derived from a different entropy must be rejected locally" - ); - } - - #[test] - fn should_normalize_wasm_uint8_array_property_without_mutating_caller_document() { - let platform_version = PlatformVersion::latest(); - let config = DataContractConfig::default_for_version(platform_version) - .expect("should create default data contract config"); - let document_type = DocumentType::try_from_schema( - contract_id(), - 1, - config.version(), - "preorder", - platform_value!({ - "type": "object", - "properties": { - "saltedDomainHash": { - "type": "array", - "byteArray": true, - "minItems": 32_u32, - "maxItems": 32_u32, - "position": 0 - } - }, - "required": ["saltedDomainHash"], - "additionalProperties": false, - }), - None, - &BTreeMap::new(), - &config, - false, - &mut Vec::new(), - platform_version, - ) - .expect("should create DPNS-like document type"); - let integer_array = Value::Array(vec![Value::U64(7); 32]); - let document = Document::V0(DocumentV0 { - id: Identifier::new([3; 32]), - owner_id: owner_id(), - properties: BTreeMap::from([("saltedDomainHash".to_string(), integer_array.clone())]), - revision: Some(INITIAL_REVISION), - ..Default::default() - }); - - let prepared = prepare_document_for_transition(&document, &document_type); - - assert_eq!( - prepared.properties().get("saltedDomainHash"), - Some(&Value::Bytes32([7; 32])) - ); - assert_eq!( - document.properties().get("saltedDomainHash"), - Some(&integer_array) - ); - } -}