Skip to content

Commit b0c3092

Browse files
authored
Identity Proof Basic Implementation (#626)
1 parent d3c76f7 commit b0c3092

File tree

22 files changed

+1123
-145
lines changed

22 files changed

+1123
-145
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# 0.4.6
22

33
* Add basic logic for implementing (social) identity proofs
4+
* Add persistence, basic service layer and WASM API for identity proofs
45

56
# 0.4.5
67

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub use bcr_ebill_core::bill;
22
pub use bcr_ebill_core::company;
33
pub use bcr_ebill_core::contact;
44
pub use bcr_ebill_core::identity;
5+
pub use bcr_ebill_core::identity_proof;
56
pub use bcr_ebill_core::mint;
67
pub use bcr_ebill_core::nostr_contact;
78
pub use bcr_ebill_core::notification;
Lines changed: 26 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use std::fmt;
2-
use std::str::FromStr;
3-
41
use async_trait::async_trait;
5-
use bcr_ebill_core::{NodeId, ServiceTraitBounds};
2+
use bcr_ebill_core::{
3+
ServiceTraitBounds,
4+
identity_proof::{IdentityProofStamp, IdentityProofStatus},
5+
};
66
use log::error;
7-
use secp256k1::{SecretKey, schnorr::Signature};
87
use thiserror::Error;
98
use url::Url;
109

@@ -29,33 +28,20 @@ pub enum Error {
2928
#[error("External Identity Proof Crypto error: {0}")]
3029
Crypto(#[from] util::crypto::Error),
3130
/// all errors originating from interacting with base58
32-
#[error("External Identity Proof Base58 error: {0}")]
33-
Base58(#[from] util::Error),
31+
#[error("External Identity Proof Validation error: {0}")]
32+
Validation(#[from] util::ValidationError),
3433
}
3534

3635
#[cfg_attr(test, automock)]
3736
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
3837
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
3938
pub trait IdentityProofApi: ServiceTraitBounds {
40-
/// Sign the base58 sha256 hash of the given node_id using the given keys and returns the resulting signature
41-
/// This is the string users are supposed to post on their social media
42-
fn create_identity_proof(
43-
&self,
44-
node_id: &NodeId,
45-
private_key: &SecretKey,
46-
) -> Result<IdentityProof>;
47-
/// Verifies that the given node_id corresponds to the given identity proof
48-
fn verify_identity_proof(
49-
&self,
50-
node_id: &NodeId,
51-
identity_proof: &IdentityProof,
52-
) -> Result<bool>;
5339
/// Checks if the given identity proof somewhere in the (successful) response of calling the given URL
5440
async fn check_url(
5541
&self,
56-
identity_proof: &IdentityProof,
42+
identity_proof_stamp: &IdentityProofStamp,
5743
url: &Url,
58-
) -> CheckIdentityProofResult;
44+
) -> IdentityProofStatus;
5945
}
6046

6147
#[derive(Debug, Clone, Default)]
@@ -79,32 +65,11 @@ impl ServiceTraitBounds for MockIdentityProofApi {}
7965
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
8066
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
8167
impl IdentityProofApi for IdentityProofClient {
82-
fn create_identity_proof(
83-
&self,
84-
node_id: &NodeId,
85-
private_key: &SecretKey,
86-
) -> Result<IdentityProof> {
87-
let hash = util::sha256_hash(node_id.to_string().as_bytes());
88-
let signature = util::crypto::signature(&hash, private_key).map_err(Error::Crypto)?;
89-
Ok(IdentityProof::from_str(&signature)?)
90-
}
91-
92-
fn verify_identity_proof(
93-
&self,
94-
node_id: &NodeId,
95-
identity_proof: &IdentityProof,
96-
) -> Result<bool> {
97-
let hash = util::sha256_hash(node_id.to_string().as_bytes());
98-
let verified = util::crypto::verify(&hash, &identity_proof.to_string(), &node_id.pub_key())
99-
.map_err(Error::Crypto)?;
100-
Ok(verified)
101-
}
102-
10368
async fn check_url(
10469
&self,
105-
identity_proof: &IdentityProof,
70+
identity_proof: &IdentityProofStamp,
10671
url: &Url,
107-
) -> CheckIdentityProofResult {
72+
) -> IdentityProofStatus {
10873
// Make an unauthenticated request to the given URL and retrieve its body
10974
match self.cl.get(url.to_owned()).send().await {
11075
Ok(res) => {
@@ -114,111 +79,48 @@ impl IdentityProofApi for IdentityProofClient {
11479
Ok(body) => {
11580
// Check if the identity proof is contained in the response
11681
if identity_proof.is_contained_in(&body) {
117-
CheckIdentityProofResult::Success
82+
IdentityProofStatus::Success
11883
} else {
119-
CheckIdentityProofResult::NotFound
84+
IdentityProofStatus::NotFound
12085
}
12186
}
12287
Err(body_err) => {
12388
error!("Error checking url: {url} for identity proof: {body_err}");
124-
CheckIdentityProofResult::FailureClient
89+
IdentityProofStatus::FailureClient
12590
}
12691
}
12792
}
12893
Err(e) => {
12994
error!("Error checking url: {url} for identity proof: {e}");
13095
if let Some(status) = e.status() {
13196
if status.is_client_error() {
132-
CheckIdentityProofResult::FailureClient
97+
IdentityProofStatus::FailureClient
13398
} else if status.is_server_error() {
134-
CheckIdentityProofResult::FailureServer
99+
IdentityProofStatus::FailureServer
135100
} else {
136-
CheckIdentityProofResult::FailureConnect
101+
IdentityProofStatus::FailureConnect
137102
}
138103
} else {
139-
CheckIdentityProofResult::FailureConnect
104+
IdentityProofStatus::FailureConnect
140105
}
141106
}
142107
}
143108
}
144109
Err(req_err) => {
145110
error!("Error checking url: {url} for identity proof: {req_err}");
146-
CheckIdentityProofResult::FailureConnect
111+
IdentityProofStatus::FailureConnect
147112
}
148113
}
149114
}
150115
}
151116

152-
#[derive(Debug, Clone)]
153-
pub struct IdentityProof {
154-
inner: Signature,
155-
}
156-
157-
impl IdentityProof {
158-
/// Checks if the identity proof signature string is within the given body of text
159-
pub fn is_contained_in(&self, body: &str) -> bool {
160-
let self_str = self.to_string();
161-
body.contains(&self_str)
162-
}
163-
}
164-
165-
impl fmt::Display for IdentityProof {
166-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167-
write!(f, "{}", util::base58_encode(&self.inner.serialize()))
168-
}
169-
}
170-
171-
impl From<Signature> for IdentityProof {
172-
fn from(value: Signature) -> Self {
173-
Self { inner: value }
174-
}
175-
}
176-
177-
impl FromStr for IdentityProof {
178-
type Err = Error;
179-
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
180-
Ok(Self {
181-
inner: Signature::from_slice(&util::base58_decode(s)?)?,
182-
})
183-
}
184-
}
185-
186-
#[derive(Debug, Clone)]
187-
pub enum CheckIdentityProofResult {
188-
/// The request succeeded and we found the signature we were looking for in the response
189-
Success,
190-
/// The request succeeded, but we didn't find the signature we were looking for in the response
191-
NotFound,
192-
/// The request failed with a connection error
193-
FailureConnect,
194-
/// The request failed with a client error (4xx)
195-
FailureClient,
196-
/// The request failed with a server error (5xx)
197-
FailureServer,
198-
}
199-
200117
#[cfg(test)]
201118
pub mod tests {
202-
use crate::tests::tests::{node_id_test, private_key_test};
203-
204-
use super::*;
205-
206-
#[test]
207-
fn test_create_and_verify() {
208-
let node_id = node_id_test();
209-
let private_key = private_key_test();
119+
use std::str::FromStr;
210120

211-
let identity_proof_client = IdentityProofClient::new();
121+
use crate::tests::tests::node_id_test;
212122

213-
let identity_proof = identity_proof_client
214-
.create_identity_proof(&node_id, &private_key)
215-
.expect("can create identity proof");
216-
assert!(
217-
identity_proof_client
218-
.verify_identity_proof(&node_id, &identity_proof)
219-
.expect("can verify identity proof")
220-
);
221-
}
123+
use super::*;
222124

223125
#[tokio::test]
224126
#[ignore]
@@ -230,32 +132,25 @@ pub mod tests {
230132
let identity_proof_client = IdentityProofClient::new();
231133

232134
// is a valid identity proof
233-
let identity_proof = IdentityProof::from_str("2DmtcWtNk2hvXaBCUAng63Gn1VDBZEojMwoZWr2VqDL5LZNgszj26YT4Pj4MUSf5o4HSmdiAEENyuNQ5UEK7zG1p").expect("is valid");
234-
assert!(
235-
identity_proof_client
236-
.verify_identity_proof(&node_id, &identity_proof)
237-
.expect("can verify identity proof")
238-
);
135+
let identity_proof = IdentityProofStamp::from_str("2DmtcWtNk2hvXaBCUAng63Gn1VDBZEojMwoZWr2VqDL5LZNgszj26YT4Pj4MUSf5o4HSmdiAEENyuNQ5UEK7zG1p").expect("is valid");
136+
assert!(identity_proof.verify_against_node_id(&node_id));
239137

240138
let valid_url = Url::parse("https://primal.net/e/nevent1qqs24kk3m0rc8e7a6f8k8daddqes0a2n74jszdszppu84e6y5q8ss3cy2rxs4").unwrap();
241139
let check_url_res = identity_proof_client
242140
.check_url(&identity_proof, &valid_url)
243141
.await;
244-
assert!(matches!(check_url_res, CheckIdentityProofResult::Success));
142+
assert!(matches!(check_url_res, IdentityProofStatus::Success));
245143

246144
let not_found_url = Url::parse("https://primal.net/e/nevent1qqsv64erdk323pkpuzqspyk3e842egaeuu8v6js970tvnyjlkjakzqc0whefs").unwrap();
247145
let check_url_res = identity_proof_client
248146
.check_url(&identity_proof, &not_found_url)
249147
.await;
250-
assert!(matches!(check_url_res, CheckIdentityProofResult::NotFound));
148+
assert!(matches!(check_url_res, IdentityProofStatus::NotFound));
251149

252150
let invalid_url = Url::parse("https://www.bit.cr/does-not-exist-ever").unwrap();
253151
let check_url_res = identity_proof_client
254152
.check_url(&identity_proof, &invalid_url)
255153
.await;
256-
assert!(matches!(
257-
check_url_res,
258-
CheckIdentityProofResult::FailureClient
259-
));
154+
assert!(matches!(check_url_res, IdentityProofStatus::FailureClient));
260155
}
261156
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use bcr_ebill_persistence::{
1010
bill::{BillChainStoreApi, BillStoreApi},
1111
company::{CompanyChainStoreApi, CompanyStoreApi},
1212
db::{
13-
email_notification::SurrealEmailNotificationStore, mint::SurrealMintStore,
13+
email_notification::SurrealEmailNotificationStore,
14+
identity_proof::SurrealIdentityProofStore, mint::SurrealMintStore,
1415
nostr_contact_store::SurrealNostrContactStore,
1516
nostr_send_queue::SurrealNostrEventQueueStore, surreal::SurrealWrapper,
1617
},
1718
file_upload::FileUploadStoreApi,
1819
identity::{IdentityChainStoreApi, IdentityStoreApi},
20+
identity_proof::IdentityProofStoreApi,
1921
mint::MintStoreApi,
2022
nostr::{NostrContactStoreApi, NostrQueuedMessageStoreApi},
2123
notification::EmailNotificationStoreApi,
@@ -53,6 +55,7 @@ pub struct DbContext {
5355
pub nostr_contact_store: Arc<dyn NostrContactStoreApi>,
5456
pub mint_store: Arc<dyn MintStoreApi>,
5557
pub nostr_chain_event_store: Arc<dyn NostrChainEventStoreApi>,
58+
pub identity_proof_store: Arc<dyn IdentityProofStoreApi>,
5659
}
5760

5861
/// Creates a new instance of the DbContext with the given SurrealDB configuration.
@@ -99,6 +102,7 @@ pub async fn get_db_context(
99102

100103
let identity_store = Arc::new(SurrealIdentityStore::new(surreal_wrapper.clone()));
101104
let identity_chain_store = Arc::new(SurrealIdentityChainStore::new(surreal_wrapper.clone()));
105+
let identity_proof_store = Arc::new(SurrealIdentityProofStore::new(surreal_wrapper.clone()));
102106
let company_chain_store = Arc::new(SurrealCompanyChainStore::new(surreal_wrapper.clone()));
103107

104108
let nostr_event_offset_store =
@@ -135,5 +139,6 @@ pub async fn get_db_context(
135139
nostr_contact_store,
136140
mint_store,
137141
nostr_chain_event_store,
142+
identity_proof_store,
138143
})
139144
}

0 commit comments

Comments
 (0)