Skip to content

Commit f901161

Browse files
authored
Restore from nostr part 2 (#605)
* Basic structure and entry point for restore * Identity processor and handler * Recover identity and company from Nostr * Version bump for nostr-sdk * Version bump rstest * Life process identity chain events * De-anonymize account when required * Review fixes * Fix processor block height
1 parent d578a48 commit f901161

31 files changed

+1571
-267
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ uuid = { version = "1", default-features = false, features = ["v4", "js"] }
4646
bitcoin = { version = "0.32", default-features = false, features = ["serde"] }
4747
bip39 = { version = "2.1", features = ["rand"] }
4848
ecies = { version = "0.2", default-features = false, features = ["pure"] }
49-
nostr = { version = "0.42" }
50-
nostr-sdk = { version = "0.42", features = ["nip04", "nip59"] }
49+
nostr = { version = "0.43" }
50+
nostr-sdk = { version = "0.43", features = ["nip04", "nip59"] }
5151
getrandom = { version = "0.3.1", features = ["wasm_js"] }
5252
async-broadcast = "0.7.2"
53-
rstest = "0.25.0"
53+
rstest = "0.26"
5454
secp256k1 = { version = "0.29" }
5555
reqwest = { version = "0.12", default-features = false }
5656
miniscript = { version = "12.3" }

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ impl CompanyServiceApi for CompanyService {
356356
&previous_block,
357357
&IdentityCreateCompanyBlockData {
358358
company_id: id.clone(),
359+
company_key: company_keys.get_private_key_string(),
359360
block_hash: create_company_block.hash.clone(),
360361
},
361362
&full_identity.key_pair,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ pub trait IdentityServiceApi: ServiceTraitBounds {
9898

9999
/// sets the active identity to the given company node id
100100
async fn set_current_company_identity(&self, node_id: &NodeId) -> Result<()>;
101+
102+
/// Returns the local key pair
103+
async fn get_keys(&self) -> Result<BcrKeys>;
101104
}
102105

103106
/// The identity service is responsible for managing the local identity
@@ -669,6 +672,10 @@ impl IdentityServiceApi for IdentityService {
669672
.await?;
670673
Ok(())
671674
}
675+
676+
async fn get_keys(&self) -> Result<BcrKeys> {
677+
Ok(self.store.get_key_pair().await?)
678+
}
672679
}
673680

674681
#[cfg(test)]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use bcr_ebill_core::util::crypto;
33

44
pub mod chain_keys;
55
pub mod event;
6+
pub mod restore;
67
mod service;
78
pub mod transport;
89

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use super::Result;
2+
use async_trait::async_trait;
3+
use bcr_ebill_core::ServiceTraitBounds;
4+
5+
#[allow(dead_code)]
6+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
7+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
8+
pub trait RestoreAccountApi: ServiceTraitBounds {
9+
/// restores the account and all the associated data
10+
async fn restore_account(&self) -> Result<()>;
11+
}

crates/bcr-ebill-api/src/service/notification_service/transport.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bcr_ebill_core::{
66
#[cfg(test)]
77
use mockall::automock;
88

9-
use nostr::Event;
9+
use nostr::{Event, Filter};
1010

1111
use super::{NostrContactData, Result, event::EventEnvelope};
1212

@@ -48,4 +48,6 @@ pub trait NotificationJsonTransportApi: ServiceTraitBounds {
4848
) -> Result<Vec<Event>>;
4949
/// Adds a new Nostr subscription on the primary client for an added contact
5050
async fn add_contact_subscription(&self, contact: &NodeId) -> Result<()>;
51+
/// Resolves all private messages matching the filter
52+
async fn resolve_private_events(&self, filter: Filter) -> Result<Vec<Event>>;
5153
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub mod tests {
1111
BlockchainType,
1212
bill::{BillBlock, BillBlockchain, BillOpCode},
1313
company::{CompanyBlock, CompanyBlockchain},
14-
identity::IdentityBlock,
14+
identity::{IdentityBlock, IdentityBlockchain},
1515
},
1616
company::{Company, CompanyKeys},
1717
contact::{BillIdentParticipant, BillParticipant, Contact, ContactType},
@@ -257,6 +257,7 @@ pub mod tests {
257257
impl IdentityChainStoreApi for IdentityChainStoreApiMock {
258258
async fn get_latest_block(&self) -> Result<IdentityBlock>;
259259
async fn add_block(&self, block: &IdentityBlock) -> Result<()>;
260+
async fn get_chain(&self) -> Result<IdentityBlockchain>;
260261
}
261262
}
262263

crates/bcr-ebill-core/src/blockchain/identity/mod.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::NodeId;
55
use crate::bill::BillId;
66
use crate::util::{self, BcrKeys, crypto};
77
use crate::{File, OptionalPostalAddress, identity::Identity};
8-
use borsh::to_vec;
8+
use borsh::{from_slice, to_vec};
99
use borsh_derive::{BorshDeserialize, BorshSerialize};
1010
use log::error;
1111
use secp256k1::PublicKey;
@@ -79,7 +79,7 @@ impl From<Identity> for IdentityCreateBlockData {
7979
}
8080
}
8181

82-
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
82+
#[derive(BorshSerialize, BorshDeserialize, Default, Debug, Clone, PartialEq)]
8383
pub struct IdentityUpdateBlockData {
8484
pub name: Option<String>,
8585
pub email: Option<String>,
@@ -112,6 +112,7 @@ pub struct IdentitySignCompanyBillBlockData {
112112
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
113113
pub struct IdentityCreateCompanyBlockData {
114114
pub company_id: NodeId,
115+
pub company_key: String,
115116
pub block_hash: String,
116117
}
117118

@@ -131,6 +132,17 @@ pub struct IdentityRemoveSignatoryBlockData {
131132
pub signatory: NodeId,
132133
}
133134

135+
#[derive(Debug)]
136+
pub enum IdentityBlockPayload {
137+
Create(IdentityCreateBlockData),
138+
Update(IdentityUpdateBlockData),
139+
SignPersonalBill(IdentitySignPersonBillBlockData),
140+
SignCompanyBill(IdentitySignCompanyBillBlockData),
141+
CreateCompany(IdentityCreateCompanyBlockData),
142+
AddSignatory(IdentityAddSignatoryBlockData),
143+
RemoveSignatory(IdentityRemoveSignatoryBlockData),
144+
}
145+
134146
impl Block for IdentityBlock {
135147
type OpCode = IdentityOpCode;
136148
type BlockDataToHash = IdentityBlockDataToHash;
@@ -393,6 +405,34 @@ impl IdentityBlock {
393405
}
394406
Ok(new_block)
395407
}
408+
409+
pub fn get_block_data(&self, keys: &BcrKeys) -> Result<IdentityBlockPayload> {
410+
let data = self.get_decrypted_block(keys)?;
411+
let result: IdentityBlockPayload = match self.op_code {
412+
IdentityOpCode::Create => IdentityBlockPayload::Create(from_slice(&data)?),
413+
IdentityOpCode::Update => IdentityBlockPayload::Update(from_slice(&data)?),
414+
IdentityOpCode::SignPersonBill => {
415+
IdentityBlockPayload::SignPersonalBill(from_slice(&data)?)
416+
}
417+
IdentityOpCode::SignCompanyBill => {
418+
IdentityBlockPayload::SignCompanyBill(from_slice(&data)?)
419+
}
420+
IdentityOpCode::CreateCompany => {
421+
IdentityBlockPayload::CreateCompany(from_slice(&data)?)
422+
}
423+
IdentityOpCode::AddSignatory => IdentityBlockPayload::AddSignatory(from_slice(&data)?),
424+
IdentityOpCode::RemoveSignatory => {
425+
IdentityBlockPayload::RemoveSignatory(from_slice(&data)?)
426+
}
427+
};
428+
Ok(result)
429+
}
430+
431+
fn get_decrypted_block(&self, keys: &BcrKeys) -> Result<Vec<u8>> {
432+
let bytes = util::base58_decode(&self.data)?;
433+
let decrypted_bytes = util::crypto::decrypt_ecies(&bytes, &keys.get_private_key())?;
434+
Ok(decrypted_bytes)
435+
}
396436
}
397437

398438
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -424,6 +464,28 @@ impl IdentityBlockchain {
424464
blocks: vec![first_block],
425465
})
426466
}
467+
468+
/// Creates an identity chain from a list of blocks
469+
pub fn new_from_blocks(blocks_to_add: Vec<IdentityBlock>) -> Result<Self> {
470+
match blocks_to_add.first() {
471+
None => Err(super::Error::BlockchainInvalid),
472+
Some(first) => {
473+
if !first.verify() || !first.validate_hash() {
474+
return Err(super::Error::BlockchainInvalid);
475+
}
476+
477+
let chain = Self {
478+
blocks: blocks_to_add,
479+
};
480+
481+
if !chain.is_chain_valid() {
482+
return Err(super::Error::BlockchainInvalid);
483+
}
484+
485+
Ok(chain)
486+
}
487+
}
488+
}
427489
}
428490

429491
#[cfg(test)]
@@ -515,6 +577,7 @@ mod tests {
515577
chain.get_latest_block(),
516578
&IdentityCreateCompanyBlockData {
517579
company_id: node_id_test(),
580+
company_key: "some key".to_string(),
518581
block_hash: "some hash".to_string(),
519582
},
520583
&keys,

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

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use super::{File, OptionalPostalAddress};
2-
use crate::{NodeId, ValidationError, util::BcrKeys};
2+
use crate::{
3+
NodeId, ValidationError,
4+
blockchain::identity::{IdentityBlockPayload, IdentityCreateBlockData},
5+
util::BcrKeys,
6+
};
37
use serde::{Deserialize, Serialize};
48

59
pub mod validation;
@@ -57,6 +61,88 @@ impl Identity {
5761
pub fn get_nostr_name(&self) -> String {
5862
self.name.clone()
5963
}
64+
65+
pub fn from_block_data(data: IdentityCreateBlockData) -> Self {
66+
Self {
67+
t: get_identity_type(&data.postal_address),
68+
node_id: data.node_id,
69+
name: data.name,
70+
email: data.email,
71+
postal_address: data.postal_address,
72+
date_of_birth: data.date_of_birth,
73+
country_of_birth: data.country_of_birth,
74+
city_of_birth: data.city_of_birth,
75+
identification_number: data.identification_number,
76+
nostr_relays: data.nostr_relays,
77+
profile_picture_file: data.profile_picture_file,
78+
identity_document_file: data.identity_document_file,
79+
}
80+
}
81+
82+
pub fn apply_block_data(&mut self, data: &IdentityBlockPayload) {
83+
// only the update block does actually mutate the identity
84+
if let IdentityBlockPayload::Update(payload) = data {
85+
// check whether the account was deanonymized with the update
86+
if self.t == IdentityType::Anon {
87+
self.t = get_identity_type(&payload.postal_address);
88+
}
89+
self.name = payload.name.to_owned().unwrap_or(self.name.to_owned());
90+
self.email = payload.email.to_owned().or(self.email.to_owned());
91+
self.postal_address.country = payload
92+
.postal_address
93+
.country
94+
.to_owned()
95+
.or(self.postal_address.country.to_owned());
96+
self.postal_address.city = payload
97+
.postal_address
98+
.city
99+
.to_owned()
100+
.or(self.postal_address.city.to_owned());
101+
self.postal_address.zip = payload
102+
.postal_address
103+
.zip
104+
.to_owned()
105+
.or(self.postal_address.zip.to_owned());
106+
self.postal_address.address = payload
107+
.postal_address
108+
.address
109+
.to_owned()
110+
.or(self.postal_address.address.to_owned());
111+
self.date_of_birth = payload
112+
.date_of_birth
113+
.to_owned()
114+
.or(self.date_of_birth.to_owned());
115+
self.country_of_birth = payload
116+
.country_of_birth
117+
.to_owned()
118+
.or(self.country_of_birth.to_owned());
119+
self.city_of_birth = payload
120+
.city_of_birth
121+
.to_owned()
122+
.or(self.city_of_birth.to_owned());
123+
self.identification_number = payload
124+
.identification_number
125+
.to_owned()
126+
.or(self.identification_number.to_owned());
127+
self.profile_picture_file = payload
128+
.profile_picture_file
129+
.to_owned()
130+
.or(self.profile_picture_file.to_owned());
131+
self.identity_document_file = payload
132+
.identity_document_file
133+
.to_owned()
134+
.or(self.identity_document_file.to_owned());
135+
}
136+
}
137+
}
138+
139+
/// determines the identity type based on the postal address
140+
fn get_identity_type(address: &OptionalPostalAddress) -> IdentityType {
141+
if address.is_fully_set() {
142+
IdentityType::Ident
143+
} else {
144+
IdentityType::Anon
145+
}
60146
}
61147

62148
#[derive(Clone, Debug)]

crates/bcr-ebill-persistence/src/db/identity_chain.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bcr_ebill_core::{
1414
PublicKey, ServiceTraitBounds,
1515
blockchain::{
1616
Block,
17-
identity::{IdentityBlock, IdentityOpCode},
17+
identity::{IdentityBlock, IdentityBlockchain, IdentityOpCode},
1818
},
1919
};
2020
use serde::{Deserialize, Serialize};
@@ -148,6 +148,28 @@ impl IdentityChainStoreApi for SurrealIdentityChainStore {
148148
Err(e) => Err(e),
149149
}
150150
}
151+
152+
async fn get_chain(&self) -> Result<IdentityBlockchain> {
153+
let mut bindings = Bindings::default();
154+
bindings.add(DB_TABLE, Self::TABLE)?;
155+
156+
let result: Vec<IdentityBlockDb> = self
157+
.db
158+
.query(
159+
"SELECT * FROM type::table($table) ORDER BY block_id ASC",
160+
bindings,
161+
)
162+
.await
163+
.map_err(|e| {
164+
log::error!("Get Identity Block: {e}");
165+
e
166+
})?;
167+
let blocks = result
168+
.into_iter()
169+
.map(|b| b.into())
170+
.collect::<Vec<IdentityBlock>>();
171+
Ok(IdentityBlockchain::new_from_blocks(blocks)?)
172+
}
151173
}
152174

153175
#[derive(Debug, Clone, Serialize, Deserialize)]

0 commit comments

Comments
 (0)