Skip to content

Commit da3048f

Browse files
authored
Share bill with external party logic (#564)
1 parent 558dfb2 commit da3048f

File tree

11 files changed

+309
-11
lines changed

11 files changed

+309
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
* Add `plaintext_hash` to Identity, Company and Bill Blocks, which is a hash over the plaintext data
44
* (breaks all chains in the DB)
5+
* Add functionality for sharing a bill with an external party, encrypted, hashed, and signed, with the plaintext block data
6+
* Change visibility of `bill_service::error` and `bill_service::service` to private, moving the used types to `bill_service`
57

68
# 0.3.17
79

crates/bcr-ebill-api/src/service/bill_service/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ use mockall::automock;
2020

2121
/// Generic result type
2222
pub type Result<T> = std::result::Result<T, error::Error>;
23+
pub use service::BillService;
2324

2425
mod blocks;
2526
mod data_fetching;
26-
pub mod error;
27+
mod error;
2728
mod issue;
2829
mod payment;
2930
mod propagation;
30-
pub mod service;
31+
mod service;
32+
mod sharing;
3133
#[cfg(test)]
3234
pub mod test_utils;
3335

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use bcr_ebill_core::{
2+
bill::{BillId, BillToShareWithExternalParty},
3+
util::BcrKeys,
4+
};
5+
use secp256k1::PublicKey;
6+
7+
use crate::util;
8+
9+
use super::{BillService, Result};
10+
11+
impl BillService {
12+
#[allow(dead_code)]
13+
/// Creates a payload of a bill, including the encrypted and plaintext block data, encrypted
14+
/// with the pub key of an external party, and signed by the sharer of the data, so the receiver
15+
/// can fully validate the bill
16+
pub(super) async fn share_bill_with_external_party(
17+
&self,
18+
bill_id: &BillId,
19+
external_party_pub_key: &PublicKey,
20+
sharer_keys: &BcrKeys,
21+
) -> Result<BillToShareWithExternalParty> {
22+
let chain = self.blockchain_store.get_chain(bill_id).await?;
23+
let bill_keys = self.store.get_keys(bill_id).await?;
24+
25+
let chain_with_plaintext = chain.get_chain_with_plaintext_block_data(&bill_keys)?;
26+
let serialized = borsh::to_vec(&chain_with_plaintext)?;
27+
let encrypted = util::crypto::encrypt_ecies(&serialized, external_party_pub_key)?;
28+
let encoded = util::base58_encode(&encrypted);
29+
30+
let hash = util::sha256_hash(&serialized);
31+
let signature = util::crypto::signature(&hash, &sharer_keys.get_private_key())?;
32+
33+
let result = BillToShareWithExternalParty {
34+
bill_id: bill_id.to_owned(),
35+
data: encoded,
36+
hash,
37+
signature,
38+
};
39+
Ok(result)
40+
}
41+
}
42+
43+
#[cfg(test)]
44+
pub mod tests {
45+
use bcr_ebill_core::blockchain::{
46+
Blockchain,
47+
bill::{
48+
BillBlock, BillBlockchain, BillOpCode,
49+
block::{BillAcceptBlockData, BillIssueBlockData},
50+
chain::BillBlockPlaintextWrapper,
51+
},
52+
};
53+
54+
use crate::{
55+
service::bill_service::test_utils::{
56+
accept_block, get_baseline_bill, get_baseline_identity, get_ctx, get_genesis_chain,
57+
get_service,
58+
},
59+
tests::tests::{bill_id_test, bill_identified_participant_only_node_id, node_id_test},
60+
};
61+
62+
use super::*;
63+
64+
#[tokio::test]
65+
async fn test_share_bill_with_external_party() {
66+
let external_party_keys = BcrKeys::new();
67+
let external_party_pub_key = external_party_keys.pub_key();
68+
let sharer_keys = BcrKeys::new();
69+
70+
let mut ctx = get_ctx();
71+
let identity = get_baseline_identity();
72+
let mut bill = get_baseline_bill(&bill_id_test());
73+
let bill_id = bill.id.clone();
74+
bill.drawee = bill_identified_participant_only_node_id(identity.identity.node_id.clone());
75+
let drawee_node_id = bill.drawee.node_id.clone();
76+
77+
ctx.bill_blockchain_store
78+
.expect_get_chain()
79+
.returning(move |_| {
80+
let mut chain = get_genesis_chain(Some(bill.clone()));
81+
chain.try_add_block(accept_block(&bill.id, chain.get_latest_block()));
82+
Ok(chain)
83+
});
84+
85+
let service = get_service(ctx);
86+
87+
let result = service
88+
.share_bill_with_external_party(&bill_id, &external_party_pub_key, &sharer_keys)
89+
.await;
90+
assert!(result.is_ok());
91+
92+
// Receiver side
93+
let unwrapped = result.unwrap().clone();
94+
assert_eq!(unwrapped.bill_id, bill_id);
95+
let data = unwrapped.data;
96+
let hash = unwrapped.hash;
97+
let signature = unwrapped.signature;
98+
// receiver can check that req was signed by the sharer
99+
assert!(util::crypto::verify(&hash, &signature, &sharer_keys.pub_key()).unwrap());
100+
let decoded = util::base58_decode(&data).unwrap();
101+
// receiver can decrypt it
102+
let decrypted =
103+
util::crypto::decrypt_ecies(&decoded, &external_party_keys.get_private_key()).unwrap();
104+
// receiver can check that hash matches the data
105+
assert_eq!(hash, util::sha256_hash(&decrypted));
106+
let deserialized: Vec<BillBlockPlaintextWrapper> = borsh::from_slice(&decrypted).unwrap();
107+
// receiver can check that plaintext hashes match
108+
for block_wrapper in deserialized.iter() {
109+
assert_eq!(
110+
block_wrapper.block.plaintext_hash,
111+
util::sha256_hash(&block_wrapper.plaintext_data_bytes)
112+
)
113+
}
114+
// receiver can check that chain is valid
115+
BillBlockchain::new_from_blocks(
116+
deserialized
117+
.iter()
118+
.map(|wrapper| wrapper.block.to_owned())
119+
.collect::<Vec<BillBlock>>(),
120+
)
121+
.unwrap();
122+
// receiver can access actual block data
123+
let issue = deserialized[0].clone();
124+
assert!(matches!(issue.block.op_code, BillOpCode::Issue));
125+
let plaintext_issue: BillIssueBlockData =
126+
borsh::from_slice(&issue.plaintext_data_bytes).unwrap();
127+
assert_eq!(plaintext_issue.id, bill_id);
128+
assert_eq!(plaintext_issue.drawee.node_id, drawee_node_id);
129+
130+
let accept = deserialized[1].clone();
131+
assert!(matches!(accept.block.op_code, BillOpCode::Accept));
132+
let plaintext_accept: BillAcceptBlockData =
133+
borsh::from_slice(&accept.plaintext_data_bytes).unwrap();
134+
assert_eq!(plaintext_accept.accepter.node_id, node_id_test());
135+
}
136+
}

crates/bcr-ebill-api/src/service/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub enum Error {
3131

3232
/// errors stemming from handling bills
3333
#[error("Bill service error: {0}")]
34-
BillService(#[from] bill_service::error::Error),
34+
BillService(#[from] bill_service::Error),
3535

3636
/// errors stemming from crypto utils
3737
#[error("Crypto util error: {0}")]

crates/bcr-ebill-core/src/bill/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,15 @@ pub struct PastPaymentDataRecourse {
610610
pub mempool_link_for_address_to_pay: String,
611611
pub status: PastPaymentStatus,
612612
}
613+
614+
#[derive(Serialize, Deserialize, Debug, Clone)]
615+
pub struct BillToShareWithExternalParty {
616+
/// The bill id
617+
pub bill_id: BillId,
618+
/// The base58 encoded, encrypted BillBlockPlaintextWrapper of the bill
619+
pub data: String,
620+
/// The hash over the unencrypted data
621+
pub hash: String,
622+
/// The signature over the hash by the sharer of the bill
623+
pub signature: String,
624+
}

crates/bcr-ebill-core/src/blockchain/bill/block.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize};
2828
use std::collections::HashSet;
2929
use std::str::FromStr;
3030

31-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
31+
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
3232
pub struct BillBlock {
3333
pub bill_id: BillId,
3434
pub id: u64,
@@ -37,6 +37,10 @@ pub struct BillBlock {
3737
pub previous_hash: String,
3838
pub timestamp: u64,
3939
pub data: String,
40+
#[borsh(
41+
serialize_with = "crate::util::borsh::serialize_pubkey",
42+
deserialize_with = "crate::util::borsh::deserialize_pubkey"
43+
)]
4044
pub public_key: PublicKey,
4145
pub signature: String,
4246
pub op_code: BillOpCode,

crates/bcr-ebill-core/src/blockchain/bill/chain.rs

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use super::super::Result;
22
use super::PaymentInfo;
33
use super::block::{
4-
BillBlock, BillEndorseBlockData, BillIdentParticipantBlockData, BillIssueBlockData,
5-
BillMintBlockData, BillOfferToSellBlockData, BillParticipantBlockData, BillRecourseBlockData,
6-
BillRequestRecourseBlockData, BillSellBlockData,
4+
BillAcceptBlockData, BillBlock, BillEndorseBlockData, BillIdentParticipantBlockData,
5+
BillIssueBlockData, BillMintBlockData, BillOfferToSellBlockData, BillParticipantBlockData,
6+
BillRecourseBlockData, BillRejectBlockData, BillRequestRecourseBlockData,
7+
BillRequestToAcceptBlockData, BillRequestToPayBlockData, BillSellBlockData,
78
};
89
use super::{BillOpCode, RecourseWaitingForPayment};
910
use super::{OfferToSellWaitingForPayment, RecoursePaymentInfo};
@@ -15,6 +16,7 @@ use crate::contact::{
1516
BillParticipant, ContactType, LightBillIdentParticipant, LightBillParticipant,
1617
};
1718
use crate::util::{self, BcrKeys};
19+
use borsh_derive::{BorshDeserialize, BorshSerialize};
1820
use log::error;
1921
use serde::{Deserialize, Serialize};
2022
use std::collections::HashMap;
@@ -27,7 +29,13 @@ pub struct BillParties {
2729
pub endorsee: Option<BillParticipantBlockData>,
2830
}
2931

30-
#[derive(Serialize, Deserialize, Debug, Clone)]
32+
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, Clone)]
33+
pub struct BillBlockPlaintextWrapper {
34+
pub block: BillBlock,
35+
pub plaintext_data_bytes: Vec<u8>,
36+
}
37+
38+
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, Clone)]
3139
pub struct BillBlockchain {
3240
blocks: Vec<BillBlock>,
3341
}
@@ -701,6 +709,81 @@ impl BillBlockchain {
701709
endorsee: last_endorsee,
702710
})
703711
}
712+
713+
/// For each block, adds the decrypted and serialized plaintext data next to it
714+
/// This is an expensive operation, since it deserialized, decrypts and reserializes the block data
715+
/// validating the integrity of the data at the end
716+
pub fn get_chain_with_plaintext_block_data(
717+
&self,
718+
bill_keys: &BillKeys,
719+
) -> Result<Vec<BillBlockPlaintextWrapper>> {
720+
let mut result = Vec::with_capacity(self.blocks().len());
721+
for block in self.blocks.iter() {
722+
let plaintext_data_bytes = match block.op_code() {
723+
BillOpCode::Issue => {
724+
borsh::to_vec(&block.get_decrypted_block::<BillIssueBlockData>(bill_keys)?)?
725+
}
726+
BillOpCode::Accept => {
727+
borsh::to_vec(&block.get_decrypted_block::<BillAcceptBlockData>(bill_keys)?)?
728+
}
729+
BillOpCode::Endorse => {
730+
borsh::to_vec(&block.get_decrypted_block::<BillEndorseBlockData>(bill_keys)?)?
731+
}
732+
BillOpCode::RequestToAccept => borsh::to_vec(
733+
&block.get_decrypted_block::<BillRequestToAcceptBlockData>(bill_keys)?,
734+
)?,
735+
BillOpCode::RequestToPay => borsh::to_vec(
736+
&block.get_decrypted_block::<BillRequestToPayBlockData>(bill_keys)?,
737+
)?,
738+
BillOpCode::OfferToSell => borsh::to_vec(
739+
&block.get_decrypted_block::<BillOfferToSellBlockData>(bill_keys)?,
740+
)?,
741+
BillOpCode::Sell => {
742+
borsh::to_vec(&block.get_decrypted_block::<BillSellBlockData>(bill_keys)?)?
743+
}
744+
BillOpCode::Mint => {
745+
borsh::to_vec(&block.get_decrypted_block::<BillMintBlockData>(bill_keys)?)?
746+
}
747+
BillOpCode::RejectToAccept => {
748+
borsh::to_vec(&block.get_decrypted_block::<BillRejectBlockData>(bill_keys)?)?
749+
}
750+
BillOpCode::RejectToPay => {
751+
borsh::to_vec(&block.get_decrypted_block::<BillRejectBlockData>(bill_keys)?)?
752+
}
753+
BillOpCode::RejectToBuy => {
754+
borsh::to_vec(&block.get_decrypted_block::<BillRejectBlockData>(bill_keys)?)?
755+
}
756+
BillOpCode::RejectToPayRecourse => {
757+
borsh::to_vec(&block.get_decrypted_block::<BillRejectBlockData>(bill_keys)?)?
758+
}
759+
BillOpCode::RequestRecourse => borsh::to_vec(
760+
&block.get_decrypted_block::<BillRequestRecourseBlockData>(bill_keys)?,
761+
)?,
762+
BillOpCode::Recourse => {
763+
borsh::to_vec(&block.get_decrypted_block::<BillRecourseBlockData>(bill_keys)?)?
764+
}
765+
};
766+
767+
if block.plaintext_hash != util::sha256_hash(&plaintext_data_bytes) {
768+
return Err(Error::BlockInvalid);
769+
}
770+
771+
result.push(BillBlockPlaintextWrapper {
772+
block: block.clone(),
773+
plaintext_data_bytes,
774+
});
775+
}
776+
777+
// Validate the chain from the wrapper
778+
BillBlockchain::new_from_blocks(
779+
result
780+
.iter()
781+
.map(|wrapper| wrapper.block.to_owned())
782+
.collect::<Vec<BillBlock>>(),
783+
)?;
784+
785+
Ok(result)
786+
}
704787
}
705788

706789
#[cfg(test)]
@@ -868,7 +951,7 @@ mod tests {
868951
node_id_last_endorsee.clone(),
869952
identity.identity.node_id.to_owned(),
870953
chain.get_first_block()
871-
),));
954+
)));
872955

873956
let keys = get_bill_keys();
874957
let result = chain.get_all_nodes_from_bill(&keys);
@@ -942,4 +1025,41 @@ mod tests {
9421025
assert_eq!(result.len(), 1);
9431026
assert_eq!(result[0].id, 2);
9441027
}
1028+
1029+
#[test]
1030+
fn test_get_serialized_chain_with_plaintext() {
1031+
let bill = empty_bitcredit_bill();
1032+
let bill_id = bill.id.clone();
1033+
let bill_keys = get_bill_keys();
1034+
let identity = get_baseline_identity();
1035+
let mut chain = BillBlockchain::new(
1036+
&BillIssueBlockData::from(bill, None, 1731593928),
1037+
identity.key_pair,
1038+
None,
1039+
BcrKeys::from_private_key(&bill_keys.private_key).unwrap(),
1040+
1731593928,
1041+
)
1042+
.unwrap();
1043+
let node_id_last_endorsee =
1044+
NodeId::new(BcrKeys::new().pub_key(), bitcoin::Network::Testnet);
1045+
assert!(chain.try_add_block(get_offer_to_sell_block(
1046+
node_id_last_endorsee.clone(),
1047+
identity.identity.node_id.to_owned(),
1048+
chain.get_first_block()
1049+
)));
1050+
1051+
let chain_with_plaintext = chain.get_chain_with_plaintext_block_data(&bill_keys);
1052+
assert!(chain_with_plaintext.is_ok());
1053+
assert_eq!(chain_with_plaintext.as_ref().unwrap().len(), 2);
1054+
1055+
let first = chain_with_plaintext.as_ref().unwrap()[0].clone();
1056+
let decrypted_block_data: BillIssueBlockData =
1057+
borsh::from_slice(&first.plaintext_data_bytes).unwrap();
1058+
assert_eq!(decrypted_block_data.id, bill_id);
1059+
1060+
let second = chain_with_plaintext.as_ref().unwrap()[1].clone();
1061+
let decrypted_block_data: BillOfferToSellBlockData =
1062+
borsh::from_slice(&second.plaintext_data_bytes).unwrap();
1063+
assert_eq!(decrypted_block_data.buyer.node_id(), node_id_last_endorsee);
1064+
}
9451065
}

0 commit comments

Comments
 (0)