diff --git a/iot_config/src/gateway_service.rs b/iot_config/src/gateway_service.rs index 67763892d..28e4a81f3 100644 --- a/iot_config/src/gateway_service.rs +++ b/iot_config/src/gateway_service.rs @@ -7,8 +7,8 @@ use file_store::traits::MsgVerify; use helium_crypto::{Keypair, PublicKey, Sign}; use helium_proto::{ services::iot_config::{ - self, GatewayLoadRegionReqV1, GatewayLoadRegionResV1, GatewayRegionParamsReqV1, - GatewayRegionParamsResV1, + self, GatewayLoadRegionReqV1, GatewayLoadRegionResV1, GatewayLocationReqV1, + GatewayLocationResV1, GatewayRegionParamsReqV1, GatewayRegionParamsResV1, }, Message, Region, }; @@ -54,6 +54,13 @@ impl GatewayService { #[tonic::async_trait] impl iot_config::Gateway for GatewayService { + async fn location( + &self, + _request: Request, + ) -> GrpcResult { + unimplemented!() + } + async fn region_params( &self, request: Request, diff --git a/poc_iot_injector/src/receipt_txn.rs b/poc_iot_injector/src/receipt_txn.rs index 8b666f299..e8ef231c4 100644 --- a/poc_iot_injector/src/receipt_txn.rs +++ b/poc_iot_injector/src/receipt_txn.rs @@ -69,7 +69,7 @@ pub fn handle_report_msg( path.push(path_element); - let (bare_txn, hash, hash_b64_url) = construct_bare_txn(path, beacon_received_ts, &keypair)?; + let (bare_txn, hash, hash_b64_url) = construct_signed_txn(path, beacon_received_ts, &keypair)?; Ok(TxnDetails { txn: wrap_txn(bare_txn), hash, @@ -102,16 +102,18 @@ fn wrap_txn(txn: BlockchainTxnPocReceiptsV2) -> BlockchainTxn { } } -fn construct_bare_txn( +fn construct_signed_txn( path: PocPath, timestamp: i64, keypair: &Keypair, ) -> Result<(BlockchainTxnPocReceiptsV2, Vec, String), TxnConstructionError> { + let strip_path = strip_path(path.clone()); + let mut txn = BlockchainTxnPocReceiptsV2 { challenger: keypair.public_key().to_vec(), secret: vec![], onion_key_hash: vec![], - path, + path: strip_path, fee: 0, signature: vec![], block_hash: vec![], @@ -119,10 +121,42 @@ fn construct_bare_txn( }; txn.signature = sign_txn(&txn, keypair)?; + + // Re-attach the original (unstripped) path back to the txn after signing + txn.path = path; + let (txn_hash, txn_hash_b64url) = hash_txn(&txn); Ok((txn, txn_hash, txn_hash_b64url)) } +/// Before signing the txn we strip the path of hex_scale and reward_unit contained in the +/// individual receipt and witnesses +fn strip_path(path: PocPath) -> PocPath { + let mut strip_path: PocPath = Vec::with_capacity(1); + for element in path { + let challengee = element.challengee; + let mut new_element = BlockchainPocPathElementV1 { + challengee, + witnesses: vec![], + receipt: None, + }; + let mut witnesses = vec![]; + for mut witness in element.witnesses { + witness.reward_unit = 0; + witness.hex_scale = 0; + witnesses.push(witness) + } + new_element.witnesses = witnesses; + if let Some(mut receipt) = element.receipt { + receipt.hex_scale = 0; + receipt.reward_unit = 0; + new_element.receipt = Some(receipt) + } + strip_path.push(new_element) + } + strip_path +} + fn construct_path_element( challengee: Vec, poc_receipt: BlockchainPocReceiptV1, @@ -141,8 +175,9 @@ fn construct_poc_witnesses( let mut poc_witnesses: Vec = Vec::with_capacity(witness_reports.len()); for witness_report in witness_reports { let witness_invalid_reason = witness_report.invalid_reason as i32; - let reward_shares = ((witness_report.hex_scale * witness_report.reward_unit) - * *REWARD_SHARE_MULTIPLIER) + let hex_scale = witness_report.hex_scale; + let reward_unit = witness_report.reward_unit; + let reward_shares = ((hex_scale * reward_unit) * *REWARD_SHARE_MULTIPLIER) .to_u32() .unwrap_or_default(); @@ -169,6 +204,8 @@ fn construct_poc_witnesses( datarate: witness_report.report.datarate.to_string(), channel: witness_invalid_reason, reward_shares, + reward_unit: reward_unit.to_u32().unwrap_or(0), + hex_scale: hex_scale.to_u32().unwrap_or(0), }; poc_witnesses.push(poc_witness) @@ -183,8 +220,9 @@ fn hz_to_mhz(freq_hz: u64) -> f32 { } fn construct_poc_receipt(beacon_report: IotValidBeaconReport) -> (BlockchainPocReceiptV1, i64) { - let reward_shares = ((beacon_report.hex_scale * beacon_report.reward_unit) - * *REWARD_SHARE_MULTIPLIER) + let hex_scale = beacon_report.hex_scale; + let reward_unit = beacon_report.reward_unit; + let reward_shares = ((hex_scale * reward_unit) * *REWARD_SHARE_MULTIPLIER) .to_u32() .unwrap_or_default(); @@ -206,6 +244,8 @@ fn construct_poc_receipt(beacon_report: IotValidBeaconReport) -> (BlockchainPocR tx_power: beacon_report.report.tx_power, addr_hash: vec![], reward_shares, + reward_unit: reward_unit.to_u32().unwrap_or(0), + hex_scale: hex_scale.to_u32().unwrap_or(0), }; (poc_receipt, beacon_received_ts) @@ -233,8 +273,99 @@ fn sign_txn( #[cfg(test)] mod tests { use super::*; + use helium_crypto::{KeyTag, KeyType, Keypair, Network, Verify}; use rust_decimal_macros::dec; + #[test] + fn txn_construction_test() { + const ENTROPY: [u8; 32] = [ + 248, 55, 78, 168, 99, 123, 22, 203, 36, 250, 136, 86, 110, 119, 198, 170, 248, 55, 78, + 168, 99, 123, 22, 203, 36, 250, 136, 86, 110, 119, 198, 170, + ]; + let kt = KeyTag { + network: Network::MainNet, + key_type: KeyType::Ed25519, + }; + let keypair = Keypair::generate_from_entropy(kt, &ENTROPY).expect("keypair"); + let pubkey = keypair.public_key(); + + let poc_witness = BlockchainPocWitnessV1 { + gateway: vec![], + timestamp: 123, + signal: 0, + packet_hash: vec![], + signature: vec![], + snr: 0.0, + frequency: 0.0, + datarate: "dr".to_string(), + channel: 0, + reward_shares: 0, + reward_unit: 2, + hex_scale: 3, + }; + let poc_receipt = BlockchainPocReceiptV1 { + gateway: vec![], + timestamp: 123, + signal: 0, + data: vec![], + origin: 0, + signature: vec![], + snr: 0.0, + frequency: 0.0, + channel: 0, + datarate: "dr".to_string(), + tx_power: -1, + addr_hash: vec![], + reward_shares: 0, + reward_unit: 1, + hex_scale: 4, + }; + let poc_path_element = BlockchainPocPathElementV1 { + challengee: vec![], + receipt: Some(poc_receipt), + witnesses: vec![poc_witness], + }; + let path = vec![poc_path_element]; + + // txn0 will be signed over the path with the reward_unit and hex_scale + let mut txn0 = BlockchainTxnPocReceiptsV2 { + challenger: keypair.public_key().to_vec(), + secret: vec![], + onion_key_hash: vec![], + path: path.clone(), + fee: 0, + signature: vec![], + block_hash: vec![], + timestamp: 456, + }; + let signature0 = sign_txn(&txn0, &keypair).expect("unable to sign txn"); + txn0.signature = signature0.clone(); + let (_txn_hash, txn0_hash_b64_url) = hash_txn(&txn0); + + // txn1 signature will be stripped of the reward_unit and hex_scale + let (txn1, _, txn1_hash_b64_url) = construct_signed_txn(path.clone(), 456, &keypair) + .expect("unable to construct signed txn"); + + // The txn hashes should be equal + assert_eq!(txn0_hash_b64_url, txn1_hash_b64_url); + + let mut txn0 = txn0; + txn0.signature = vec![]; + // This txn0 should be verifiable as is + assert!(pubkey.verify(&txn0.encode_to_vec(), &signature0).is_ok()); + + let signature1 = txn1.clone().signature; + + // The two signatures should be different + assert_ne!(signature0, signature1); + + let mut txn1 = txn1; + txn1.signature = vec![]; + // For txn1, we strip the path before verifying + txn1.path = strip_path(path); + assert!(pubkey.verify(&txn1.encode_to_vec(), &signature1).is_ok()); + } + #[test] fn max_witnesses_per_receipt_test() { let poc_witness = BlockchainPocWitnessV1 { @@ -248,6 +379,8 @@ mod tests { datarate: "dr".to_string(), channel: 0, reward_shares: 0, + reward_unit: 0, + hex_scale: 0, }; let poc_id: Vec = vec![0]; let max_witnesses_per_receipt = 14;