From 853e699a9d402cb0a3f8ecec2dfdffc991e0d0a7 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Fri, 29 Nov 2024 19:16:58 +0500 Subject: [PATCH 01/12] Add `serde-with` dependency --- Cargo.lock | 77 +++++++++++++++++++++++++++++ pallets/ddc-verification/Cargo.toml | 1 + 2 files changed, 78 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 920cc8c64..4c1d04c6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,6 +755,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -1515,6 +1521,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] @@ -2058,6 +2065,41 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.85", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.85", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -3638,6 +3680,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -5792,6 +5840,7 @@ dependencies = [ "scale-info", "serde", "serde_json", + "serde_with", "sp-application-crypto", "sp-core", "sp-io", @@ -9045,6 +9094,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "sha-1" version = "0.9.8" diff --git a/pallets/ddc-verification/Cargo.toml b/pallets/ddc-verification/Cargo.toml index f96474491..39adbc056 100644 --- a/pallets/ddc-verification/Cargo.toml +++ b/pallets/ddc-verification/Cargo.toml @@ -29,6 +29,7 @@ rand = { workspace = true, features = ["small_rng", "alloc"], default-features = scale-info = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +serde_with = { version = "3", default-features = false, features = ["base64", "macros"] } sp-application-crypto = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } From b92186427c9e26e65bf823dbb099199e457bbbc1 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Fri, 29 Nov 2024 19:18:21 +0500 Subject: [PATCH 02/12] Add aggregator response wrapper with a signature --- pallets/ddc-verification/src/aggregator_client.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pallets/ddc-verification/src/aggregator_client.rs b/pallets/ddc-verification/src/aggregator_client.rs index c7c14b00c..182121d2c 100644 --- a/pallets/ddc-verification/src/aggregator_client.rs +++ b/pallets/ddc-verification/src/aggregator_client.rs @@ -2,6 +2,7 @@ use ddc_primitives::{AggregatorInfo, BucketId, DdcEra}; use prost::Message; +use serde_with::{base64::Base64, serde_as}; use sp_io::offchain::timestamp; use sp_runtime::offchain::{http, Duration}; @@ -430,4 +431,17 @@ pub(crate) mod json { pub number_of_puts: u64, pub number_of_gets: u64, } + + /// Json response wrapped with a signature. + #[serde_as] + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct SignedJsonResponse { + pub payload: T, + #[serde_as(as = "Base64")] + pub signer: Vec, + #[serde_as(as = "Base64")] + pub signature: Vec, + } } From 89cde96b121f5216d06dc242beeb6535245fa4af Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Fri, 29 Nov 2024 19:19:23 +0500 Subject: [PATCH 03/12] Impl JSON resp signature verification --- pallets/ddc-verification/src/signature.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pallets/ddc-verification/src/signature.rs b/pallets/ddc-verification/src/signature.rs index b65b79fa9..2069666b4 100644 --- a/pallets/ddc-verification/src/signature.rs +++ b/pallets/ddc-verification/src/signature.rs @@ -1,3 +1,4 @@ +use aggregator_client::json; use prost::Message; use sp_core::ed25519::{Public, Signature}; use sp_io::crypto::ed25519_verify; @@ -99,6 +100,27 @@ impl Verify for proto::ChallengeResponse { } } +impl Verify for json::SignedJsonResponse { + fn verify(&self) -> bool { + let sig = match Signature::try_from(self.signature.as_slice()) { + Ok(s) => s, + Err(_) => return false, + }; + + let payload = match serde_json::to_vec(&self.payload) { + Ok(p) => p, + Err(_) => return false, + }; + + let pub_key = match Public::try_from(self.signer.as_slice()) { + Ok(p) => p, + Err(_) => return false, + }; + + ed25519_verify(&sig, payload.as_slice(), &pub_key) + } +} + trait Signed { fn get_signature(&self) -> Option<&proto::Signature>; fn reset_signature(&mut self); From 0611423d9cef6e81329e1db5066479ac4e0f0773 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Fri, 29 Nov 2024 19:20:22 +0500 Subject: [PATCH 04/12] Error on JSON signature verification failure --- .../ddc-verification/src/aggregator_client.rs | 76 ++++++++++++++----- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/pallets/ddc-verification/src/aggregator_client.rs b/pallets/ddc-verification/src/aggregator_client.rs index 182121d2c..480f50362 100644 --- a/pallets/ddc-verification/src/aggregator_client.rs +++ b/pallets/ddc-verification/src/aggregator_client.rs @@ -7,16 +7,18 @@ use sp_io::offchain::timestamp; use sp_runtime::offchain::{http, Duration}; use super::*; +use crate::signature::Verify; pub struct AggregatorClient<'a> { pub base_url: &'a str, timeout: Duration, retries: u32, + verify_sig: bool, } impl<'a> AggregatorClient<'a> { - pub fn new(base_url: &'a str, timeout: Duration, retries: u32) -> Self { - Self { base_url, timeout, retries } + pub fn new(base_url: &'a str, timeout: Duration, retries: u32, verify_sig: bool) -> Self { + Self { base_url, timeout, retries, verify_sig } } pub fn buckets_aggregates( @@ -32,11 +34,19 @@ impl<'a> AggregatorClient<'a> { if let Some(prev_token) = prev_token { url = format!("{}&prevToken={}", url, prev_token); } + if self.verify_sig { + url = format!("{}&sign=true", url); + } let response = self.get(&url, Accept::Any)?; let body = response.body().collect::>(); - let json_response = serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + let json_response: json::SignedJsonResponse> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + if self.verify_sig && !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response) + Ok(json_response.payload) } pub fn nodes_aggregates( @@ -52,11 +62,19 @@ impl<'a> AggregatorClient<'a> { if let Some(prev_token) = prev_token { url = format!("{}&prevToken={}", url, prev_token); } + if self.verify_sig { + url = format!("{}&sign=true", url); + } let response = self.get(&url, Accept::Any)?; let body = response.body().collect::>(); - let json_response = serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + let json_response: json::SignedJsonResponse> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - Ok(json_response) + if self.verify_sig && !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } + + Ok(json_response.payload) } pub fn challenge_bucket_sub_aggregate( @@ -104,12 +122,20 @@ impl<'a> AggregatorClient<'a> { } pub fn eras(&self) -> Result, http::Error> { - let url = format!("{}/activity/eras", self.base_url); + let mut url = format!("{}/activity/eras", self.base_url); + if self.verify_sig { + url = format!("{}&sign=true", url); + } let response = self.get(&url, Accept::Any)?; let body = response.body().collect::>(); - let json_response = serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + let json_response: json::SignedJsonResponse> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + if self.verify_sig && !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response) + Ok(json_response.payload) } pub fn traverse_bucket_sub_aggregate( @@ -120,16 +146,24 @@ impl<'a> AggregatorClient<'a> { merkle_tree_node_id: u32, levels: u16, ) -> Result { - let url = format!( - "{}/activity/buckets/{}/traverse?eraId={}&nodeId={}&merkleTreeNodeId={}&levels={}", + let mut url = format!( + "{}/activity/buckets/{}/traverse?eraId={}&nodeId={}&merkleTreeNodeId={}&levels={}&signed=true", self.base_url, bucket_id, era_id, node_id, merkle_tree_node_id, levels, ); + if self.verify_sig { + url = format!("{}&sign=true", url); + } let response = self.get(&url, Accept::Any)?; let body = response.body().collect::>(); - let json_response = serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + let json_response: json::SignedJsonResponse = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + if self.verify_sig && !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response) + Ok(json_response.payload) } pub fn traverse_node_aggregate( @@ -139,16 +173,24 @@ impl<'a> AggregatorClient<'a> { merkle_tree_node_id: u32, levels: u16, ) -> Result { - let url = format!( - "{}/activity/nodes/{}/traverse?eraId={}&merkleTreeNodeId={}&levels={}", + let mut url = format!( + "{}/activity/nodes/{}/traverse?eraId={}&merkleTreeNodeId={}&levels={}&signed=true", self.base_url, node_id, era_id, merkle_tree_node_id, levels, ); + if self.verify_sig { + url = format!("{}&sign=true", url); + } let response = self.get(&url, Accept::Any)?; let body = response.body().collect::>(); - let json_response = serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + let json_response: json::SignedJsonResponse = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + if self.verify_sig && !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response) + Ok(json_response.payload) } fn merkle_tree_node_id_param(merkle_tree_node_id: &[u32]) -> String { From b55c09087d44a528f5ed97677dd77ddde9be1731 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Fri, 29 Nov 2024 19:21:12 +0500 Subject: [PATCH 05/12] Enable JSON resp signature verification --- pallets/ddc-verification/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pallets/ddc-verification/src/lib.rs b/pallets/ddc-verification/src/lib.rs index b17a3724a..2461ca6e3 100644 --- a/pallets/ddc-verification/src/lib.rs +++ b/pallets/ddc-verification/src/lib.rs @@ -2982,6 +2982,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, + true, ); match aggregate_key { @@ -3018,6 +3019,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, + true, ); let response = match aggregate_key { @@ -3050,6 +3052,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, + true, ); let response = client.eras()?; @@ -3073,6 +3076,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, + true, ); let mut buckets_aggregates = Vec::new(); @@ -3115,6 +3119,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, + true, ); let mut nodes_aggregates = Vec::new(); From 25c309e101b9ae1d41934df99743aca11a0f52f3 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Fri, 29 Nov 2024 19:24:33 +0500 Subject: [PATCH 06/12] Unit test for a signed JSON response verification --- .../activity_buckets_signed_resp.json | 36 +++++++++++++++ pallets/ddc-verification/src/tests.rs | 44 ++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 pallets/ddc-verification/src/test_data/activity_buckets_signed_resp.json diff --git a/pallets/ddc-verification/src/test_data/activity_buckets_signed_resp.json b/pallets/ddc-verification/src/test_data/activity_buckets_signed_resp.json new file mode 100644 index 000000000..d5d007d3d --- /dev/null +++ b/pallets/ddc-verification/src/test_data/activity_buckets_signed_resp.json @@ -0,0 +1,36 @@ +{ + "payload": [ + { + "bucket_id": 1, + "stored_bytes": 3145728, + "transferred_bytes": 6291456, + "number_of_puts": 3, + "number_of_gets": 3, + "sub_aggregates": [ + { + "NodeID": "0x0ac7cb9c53594e9f538d9950c6bcf28f0c0c7b8385deea2ebe24062bc640e7be", + "stored_bytes": 3145728, + "transferred_bytes": 4194304, + "number_of_puts": 3, + "number_of_gets": 1 + }, + { + "NodeID": "0x7adf9ca8b587d3b2184d628baac385b43a247b981a66e20ecc89e15a7a273ca5", + "stored_bytes": 0, + "transferred_bytes": 1048576, + "number_of_puts": 0, + "number_of_gets": 1 + }, + { + "NodeID": "0xc4751f725bf42932f20b3d4d8ec2c074cac405f29d96d6b5df45a2101bc1d78b", + "stored_bytes": 0, + "transferred_bytes": 1048576, + "number_of_puts": 0, + "number_of_gets": 1 + } + ] + } + ], + "signer": "xHUfclv0KTLyCz1NjsLAdMrEBfKdlta130WiEBvB14s=", + "signature": "XCyDGX+MRqpbRUng54h3UJ5rklshT92FTYfKF2WLR81Aovf4ZpqqoObOoBn7+g2XXf8vX+RY3dXceBipJ8M6CA==" +} diff --git a/pallets/ddc-verification/src/tests.rs b/pallets/ddc-verification/src/tests.rs index a308d76f9..af1058d59 100644 --- a/pallets/ddc-verification/src/tests.rs +++ b/pallets/ddc-verification/src/tests.rs @@ -3097,7 +3097,49 @@ fn challenge_bucket_sub_aggregate_works() { }); } -use crate::aggregator_client::AggregatorClient; +use crate::aggregator_client::{ + json::{BucketAggregateResponse, SignedJsonResponse}, + AggregatorClient, +}; + +#[test] +fn aggregator_client_get_buckets_aggregates_works() { + let mut ext = TestExternalities::default(); + let (offchain, offchain_state) = TestOffchainExt::new(); + + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(OffchainDbExt::new(Box::new(offchain))); + + ext.execute_with(|| { + let mut offchain_state = offchain_state.write(); + offchain_state.timestamp = Timestamp::from_unix_millis(0); + + let base_url = "http://example.com:8080"; + let era_id = 346524624; + let activity_buckets_signed_resp = + include_bytes!("./test_data/activity_buckets_signed_resp.json").as_slice(); + + let expected_request = PendingRequest { + method: "GET".to_string(), + uri: format!("{}/activity/buckets?eraId={}&sign=true", base_url, era_id), + response: Some(activity_buckets_signed_resp.to_vec()), + sent: true, + ..Default::default() + }; + + offchain_state.expect_request(expected_request); + drop(offchain_state); + + let client = AggregatorClient::new(base_url, Duration::from_millis(1_000), 1, true); + + let expected_response: SignedJsonResponse> = + serde_json::from_slice(activity_buckets_signed_resp) + .expect("json parsing failed, broken test data?"); + let result = client.buckets_aggregates(era_id, None, None); + + assert_eq!(result, Ok(expected_response.payload)); + }) +} #[test] fn aggregator_client_challenge_bucket_sub_aggregate_works() { From 53e3fbf6e02de232f6bae74fbf10186eb903b50a Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Mon, 2 Dec 2024 15:28:58 +0500 Subject: [PATCH 07/12] Remove duplicate query param --- pallets/ddc-verification/src/aggregator_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/ddc-verification/src/aggregator_client.rs b/pallets/ddc-verification/src/aggregator_client.rs index 480f50362..5ad98dd20 100644 --- a/pallets/ddc-verification/src/aggregator_client.rs +++ b/pallets/ddc-verification/src/aggregator_client.rs @@ -147,7 +147,7 @@ impl<'a> AggregatorClient<'a> { levels: u16, ) -> Result { let mut url = format!( - "{}/activity/buckets/{}/traverse?eraId={}&nodeId={}&merkleTreeNodeId={}&levels={}&signed=true", + "{}/activity/buckets/{}/traverse?eraId={}&nodeId={}&merkleTreeNodeId={}&levels={}", self.base_url, bucket_id, era_id, node_id, merkle_tree_node_id, levels, ); if self.verify_sig { @@ -174,7 +174,7 @@ impl<'a> AggregatorClient<'a> { levels: u16, ) -> Result { let mut url = format!( - "{}/activity/nodes/{}/traverse?eraId={}&merkleTreeNodeId={}&levels={}&signed=true", + "{}/activity/nodes/{}/traverse?eraId={}&merkleTreeNodeId={}&levels={}", self.base_url, node_id, era_id, merkle_tree_node_id, levels, ); if self.verify_sig { From 5869f4fe95d51c0a1e2169937917a9d4e214a006 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Mon, 2 Dec 2024 15:30:13 +0500 Subject: [PATCH 08/12] Fix unsigned aggregator resp deserialization --- .../ddc-verification/src/aggregator_client.rs | 101 ++++++++++++------ 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/pallets/ddc-verification/src/aggregator_client.rs b/pallets/ddc-verification/src/aggregator_client.rs index 5ad98dd20..b5391f438 100644 --- a/pallets/ddc-verification/src/aggregator_client.rs +++ b/pallets/ddc-verification/src/aggregator_client.rs @@ -38,15 +38,23 @@ impl<'a> AggregatorClient<'a> { url = format!("{}&sign=true", url); } let response = self.get(&url, Accept::Any)?; + let body = response.body().collect::>(); - let json_response: json::SignedJsonResponse> = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + if self.verify_sig { + let json_response: json::SignedJsonResponse> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - if self.verify_sig && !json_response.verify() { - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } + if !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response.payload) + Ok(json_response.payload) + } else { + let json_response: Vec = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + Ok(json_response) + } } pub fn nodes_aggregates( @@ -66,15 +74,23 @@ impl<'a> AggregatorClient<'a> { url = format!("{}&sign=true", url); } let response = self.get(&url, Accept::Any)?; + let body = response.body().collect::>(); - let json_response: json::SignedJsonResponse> = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + if self.verify_sig { + let json_response: json::SignedJsonResponse> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - if self.verify_sig && !json_response.verify() { - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } + if !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response.payload) + Ok(json_response.payload) + } else { + let json_response: Vec = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + Ok(json_response) + } } pub fn challenge_bucket_sub_aggregate( @@ -127,15 +143,23 @@ impl<'a> AggregatorClient<'a> { url = format!("{}&sign=true", url); } let response = self.get(&url, Accept::Any)?; + let body = response.body().collect::>(); - let json_response: json::SignedJsonResponse> = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + if self.verify_sig { + let json_response: json::SignedJsonResponse> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - if self.verify_sig && !json_response.verify() { - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } + if !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response.payload) + Ok(json_response.payload) + } else { + let json_response: Vec = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + Ok(json_response) + } } pub fn traverse_bucket_sub_aggregate( @@ -155,15 +179,23 @@ impl<'a> AggregatorClient<'a> { } let response = self.get(&url, Accept::Any)?; + let body = response.body().collect::>(); - let json_response: json::SignedJsonResponse = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + if self.verify_sig { + let json_response: json::SignedJsonResponse = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - if self.verify_sig && !json_response.verify() { - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } + if !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } - Ok(json_response.payload) + Ok(json_response.payload) + } else { + let json_response: json::MerkleTreeNodeResponse = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + Ok(json_response) + } } pub fn traverse_node_aggregate( @@ -180,17 +212,24 @@ impl<'a> AggregatorClient<'a> { if self.verify_sig { url = format!("{}&sign=true", url); } - let response = self.get(&url, Accept::Any)?; + let body = response.body().collect::>(); - let json_response: json::SignedJsonResponse = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + if self.verify_sig { + let json_response: json::SignedJsonResponse = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - if self.verify_sig && !json_response.verify() { - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } + if !json_response.verify() { + return Err(http::Error::Unknown); // TODO (khssnv): more specific error. + } + + Ok(json_response.payload) + } else { + let json_response: json::MerkleTreeNodeResponse = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - Ok(json_response.payload) + Ok(json_response) + } } fn merkle_tree_node_id_param(merkle_tree_node_id: &[u32]) -> String { From be1d48ff2c224e32d451fa007cf17d8043d3d86d Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Mon, 2 Dec 2024 15:31:27 +0500 Subject: [PATCH 09/12] Pallet parameter to enable aggregator resp sig --- pallets/ddc-verification/src/lib.rs | 11 ++++++----- runtime/cere-dev/src/lib.rs | 1 + runtime/cere/src/lib.rs | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pallets/ddc-verification/src/lib.rs b/pallets/ddc-verification/src/lib.rs index 2461ca6e3..56a8551e4 100644 --- a/pallets/ddc-verification/src/lib.rs +++ b/pallets/ddc-verification/src/lib.rs @@ -163,6 +163,7 @@ pub mod pallet { type AccountIdConverter: From + Into; type CustomerVisitor: CustomerVisitor; type Currency: Currency; + const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor: CustomerDepositor; #[cfg(feature = "runtime-benchmarks")] @@ -2982,7 +2983,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, - true, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, ); match aggregate_key { @@ -3019,7 +3020,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, - true, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, ); let response = match aggregate_key { @@ -3052,7 +3053,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, - true, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, ); let response = client.eras()?; @@ -3076,7 +3077,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, - true, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, ); let mut buckets_aggregates = Vec::new(); @@ -3119,7 +3120,7 @@ pub mod pallet { &base_url, Duration::from_millis(RESPONSE_TIMEOUT), 3, - true, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, ); let mut nodes_aggregates = Vec::new(); diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index e9bd500b1..78e0c267a 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -1328,6 +1328,7 @@ impl pallet_ddc_verification::Config for Runtime { type CustomerVisitor = pallet_ddc_customers::Pallet; const MAX_MERKLE_NODE_IDENTIFIER: u16 = 3; type Currency = Balances; + const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = true; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor = DdcCustomers; #[cfg(feature = "runtime-benchmarks")] diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 40ab94a8d..28f6d2b43 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -1348,6 +1348,7 @@ impl pallet_ddc_verification::Config for Runtime { type CustomerVisitor = pallet_ddc_customers::Pallet; const MAX_MERKLE_NODE_IDENTIFIER: u16 = 3; type Currency = Balances; + const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = true; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor = DdcCustomers; #[cfg(feature = "runtime-benchmarks")] From a120238639e7268a02fa188e504d7ae7a91044ac Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Mon, 2 Dec 2024 15:33:04 +0500 Subject: [PATCH 10/12] Disable aggregates resp sig verification for tests --- pallets/ddc-verification/src/mock.rs | 2 ++ pallets/ddc-verification/src/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pallets/ddc-verification/src/mock.rs b/pallets/ddc-verification/src/mock.rs index 677d2c3ad..64fa0de52 100644 --- a/pallets/ddc-verification/src/mock.rs +++ b/pallets/ddc-verification/src/mock.rs @@ -220,6 +220,7 @@ impl pallet_timestamp::Config for Test { parameter_types! { pub const VerificationPalletId: PalletId = PalletId(*b"verifypa"); pub const MajorityOfAggregators: Percent = Percent::from_percent(67); + pub const VerifyAggregatorResponseSignature: bool = false; } impl crate::Config for Test { @@ -245,6 +246,7 @@ impl crate::Config for Test { type CustomerVisitor = MockCustomerVisitor; const MAX_MERKLE_NODE_IDENTIFIER: u16 = 4; type Currency = Balances; + const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = false; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor = MockCustomerDepositor; #[cfg(feature = "runtime-benchmarks")] diff --git a/pallets/ddc-verification/src/tests.rs b/pallets/ddc-verification/src/tests.rs index af1058d59..ef31aa139 100644 --- a/pallets/ddc-verification/src/tests.rs +++ b/pallets/ddc-verification/src/tests.rs @@ -3190,7 +3190,7 @@ fn aggregator_client_challenge_bucket_sub_aggregate_works() { offchain_state.expect_request(expected); drop(offchain_state); - let client = AggregatorClient::new(base_url, Duration::from_millis(1_000), 1); + let client = AggregatorClient::new(base_url, Duration::from_millis(1_000), 1, false); let result = client.challenge_bucket_sub_aggregate(era_id, bucket_id, node_id, vec![2, 6]); assert_eq!(result, Ok(expected_response)); @@ -3245,7 +3245,7 @@ fn aggregator_client_challenge_node_aggregate_works() { offchain_state.expect_request(expected); drop(offchain_state); - let client = AggregatorClient::new(base_url, Duration::from_millis(1_000), 1); + let client = AggregatorClient::new(base_url, Duration::from_millis(1_000), 1, false); let result = client.challenge_node_aggregate(era_id, node_id, vec![2, 6]); assert_eq!(result, Ok(expected_response)); From 18f672f2ef1bd339511490089448b274739b8b0f Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Mon, 2 Dec 2024 16:53:11 +0500 Subject: [PATCH 11/12] Bump `spec_version` --- runtime/cere-dev/src/lib.rs | 2 +- runtime/cere/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index 78e0c267a..1f723ae65 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -153,7 +153,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 61008, + spec_version: 61009, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 23, diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 28f6d2b43..e03bbc17a 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -147,7 +147,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 61008, + spec_version: 61009, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 23, From d9e6d91c8a1a94d95176318fc2820b04a5a598ef Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Tue, 3 Dec 2024 13:21:10 +0500 Subject: [PATCH 12/12] Log debug aggregator response bad signatures --- pallets/ddc-verification/src/aggregator_client.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pallets/ddc-verification/src/aggregator_client.rs b/pallets/ddc-verification/src/aggregator_client.rs index b5391f438..f60f9207a 100644 --- a/pallets/ddc-verification/src/aggregator_client.rs +++ b/pallets/ddc-verification/src/aggregator_client.rs @@ -45,6 +45,7 @@ impl<'a> AggregatorClient<'a> { serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; if !json_response.verify() { + log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); return Err(http::Error::Unknown); // TODO (khssnv): more specific error. } @@ -81,6 +82,7 @@ impl<'a> AggregatorClient<'a> { serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; if !json_response.verify() { + log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); return Err(http::Error::Unknown); // TODO (khssnv): more specific error. } @@ -150,6 +152,7 @@ impl<'a> AggregatorClient<'a> { serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; if !json_response.verify() { + log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); return Err(http::Error::Unknown); // TODO (khssnv): more specific error. } @@ -186,6 +189,7 @@ impl<'a> AggregatorClient<'a> { serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; if !json_response.verify() { + log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); return Err(http::Error::Unknown); // TODO (khssnv): more specific error. } @@ -220,6 +224,7 @@ impl<'a> AggregatorClient<'a> { serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; if !json_response.verify() { + log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); return Err(http::Error::Unknown); // TODO (khssnv): more specific error. }