Skip to content

Commit 36133f4

Browse files
authored
add node id type, add bill id (#550)
1 parent 59d0a9e commit 36133f4

File tree

10 files changed

+512
-46
lines changed

10 files changed

+512
-46
lines changed

.github/workflows/rust.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
push:
55
branches: [ "*" ]
66

7+
permissions:
8+
contents: read
9+
security-events: write
10+
711
env:
812
CARGO_TERM_COLOR: always
913

@@ -26,7 +30,7 @@ jobs:
2630
df -h
2731
2832
- name: Switch rust version
29-
run: rustup default nightly
33+
run: rustup default stable
3034

3135
- name: Update rust
3236
run: rustup update

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
push:
55
branches: [ "*" ]
66

7+
permissions:
8+
contents: read
9+
security-events: write
10+
711
jobs:
812
test:
913
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
* Nostr npub as primary key in Nostr contacts (breaking DB change)
55
* Add default mint to nostr contacts as default, so it doesn't have to be added to contacts anymore
66

7+
# 0.3.15-hotfix1
8+
9+
* Fix Bill Caching issue between multiple identities
10+
711
# 0.3.15
812

913
* Upload and download files to and from Nostr using Blossom

crates/bcr-ebill-api/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ bcr-ebill-transport = { path = "../bcr-ebill-transport" }
3131
tokio.workspace = true
3232
tokio_with_wasm.workspace = true
3333
secp256k1.workspace = true
34-
bcr-wdc-webapi = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "210ee9fccfb93e00247c46dbfcd4cc55287" }
35-
bcr-wdc-quote-client = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "210ee9fccfb93e00247c46dbfcd4cc55287" }
36-
bcr-wdc-key-client = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "210ee9fccfb93e00247c46dbfcd4cc55287" }
37-
bcr-wdc-swap-client = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "210ee9fccfb93e00247c46dbfcd4cc55287" }
34+
bcr-wdc-webapi = { git = "https://github.com/BitcreditProtocol/wildcat", tag = "v0.2.0" }
35+
bcr-wdc-quote-client = { git = "https://github.com/BitcreditProtocol/wildcat", tag = "v0.2.0" }
36+
bcr-wdc-key-client = { git = "https://github.com/BitcreditProtocol/wildcat", tag = "v0.2.0" }
37+
bcr-wdc-swap-client = { git = "https://github.com/BitcreditProtocol/wildcat", tag = "v0.2.0" }
3838
cashu = { version = "0.9", default-features = false }
3939
rand = { version = "0.8" }
4040
hex = { version = "0.4" }

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

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
use std::str::FromStr;
2+
13
use crate::{
4+
ID_PREFIX, NETWORK_MAINNET, NETWORK_REGTEST, NETWORK_TESTNET, NETWORK_TESTNET4,
5+
ValidationError,
26
blockchain::bill::{BillBlockchain, block::NodeId},
37
contact::{BillParticipant, LightBillParticipant},
4-
util::BcrKeys,
8+
network_char,
9+
util::{self, BcrKeys},
510
};
611

712
use super::{
@@ -16,6 +21,118 @@ use serde::{Deserialize, Serialize};
1621

1722
pub mod validation;
1823

24+
/// A bitcr Bill ID of the format <prefix><network><hash>
25+
/// Example: bitcrtBBT5a1eNZ8zEUkU2rppXBDrZJjARoxPkZtBgFo2RLz3y
26+
/// The prefix is bitcr
27+
/// The pub key is a base58 encoded, sha256 hashed Secp256k1 public key (the bill pub key)
28+
/// The network character can be parsed like this:
29+
/// * m => Mainnet
30+
/// * t => Testnet
31+
/// * T => Testnet4
32+
/// * r => Regtest
33+
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
34+
pub struct BillId {
35+
hash: String,
36+
network: bitcoin::Network,
37+
}
38+
39+
impl BillId {
40+
pub fn new(hash: String, network: bitcoin::Network) -> Self {
41+
Self { hash, network }
42+
}
43+
44+
pub fn network(&self) -> bitcoin::Network {
45+
self.network
46+
}
47+
48+
pub fn id(&self) -> &str {
49+
&self.hash
50+
}
51+
}
52+
53+
impl std::fmt::Display for BillId {
54+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55+
write!(
56+
f,
57+
"{}{}{}",
58+
ID_PREFIX,
59+
network_char(&self.network),
60+
self.hash
61+
)
62+
}
63+
}
64+
65+
impl FromStr for BillId {
66+
type Err = ValidationError;
67+
68+
fn from_str(s: &str) -> Result<Self, Self::Err> {
69+
if !s.starts_with(ID_PREFIX) {
70+
return Err(ValidationError::InvalidBillId);
71+
}
72+
73+
let network = match s.chars().nth(ID_PREFIX.len()) {
74+
None => {
75+
return Err(ValidationError::InvalidBillId);
76+
}
77+
Some(network_str) => match network_str {
78+
NETWORK_MAINNET => bitcoin::Network::Bitcoin,
79+
NETWORK_TESTNET => bitcoin::Network::Testnet,
80+
NETWORK_TESTNET4 => bitcoin::Network::Testnet4,
81+
NETWORK_REGTEST => bitcoin::Network::Regtest,
82+
_ => {
83+
return Err(ValidationError::InvalidBillId);
84+
}
85+
},
86+
};
87+
88+
let hash_str = &s[ID_PREFIX.len() + 1..];
89+
let decoded = util::base58_decode(hash_str).map_err(|_| ValidationError::InvalidBillId)?;
90+
// sha256 is always 32 bytes
91+
if decoded.len() != 32 {
92+
return Err(ValidationError::InvalidBillId);
93+
}
94+
95+
Ok(Self {
96+
hash: hash_str.to_owned(),
97+
network,
98+
})
99+
}
100+
}
101+
102+
impl serde::Serialize for BillId {
103+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
104+
where
105+
S: serde::Serializer,
106+
{
107+
s.collect_str(self)
108+
}
109+
}
110+
111+
impl<'de> serde::Deserialize<'de> for BillId {
112+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
113+
where
114+
D: serde::Deserializer<'de>,
115+
{
116+
let s = String::deserialize(d)?;
117+
BillId::from_str(&s).map_err(serde::de::Error::custom)
118+
}
119+
}
120+
121+
impl borsh::BorshSerialize for BillId {
122+
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
123+
let bill_id_str = self.to_string();
124+
borsh::BorshSerialize::serialize(&bill_id_str, writer)
125+
}
126+
}
127+
128+
impl borsh::BorshDeserialize for BillId {
129+
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
130+
let bill_id_str: String = borsh::BorshDeserialize::deserialize_reader(reader)?;
131+
BillId::from_str(&bill_id_str)
132+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
133+
}
134+
}
135+
19136
#[derive(Debug, Clone, PartialEq, Eq)]
20137
pub enum BillAction {
21138
RequestAcceptance,

crates/bcr-ebill-core/src/lib.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use borsh_derive::{BorshDeserialize, BorshSerialize};
33
use company::Company;
44
use contact::Contact;
55
use serde::{Deserialize, Serialize};
6-
use std::{fmt, pin::Pin};
6+
use std::{fmt, pin::Pin, str::FromStr};
77
use thiserror::Error;
88
use util::is_blank;
99

@@ -20,6 +20,137 @@ pub mod notification;
2020
mod tests;
2121
pub mod util;
2222

23+
const ID_PREFIX: &str = "bitcr";
24+
const NETWORK_MAINNET: char = 'm';
25+
const NETWORK_TESTNET: char = 't';
26+
const NETWORK_TESTNET4: char = 'T';
27+
const NETWORK_REGTEST: char = 'r';
28+
29+
fn network_char(network: &bitcoin::Network) -> char {
30+
match network {
31+
bitcoin::Network::Bitcoin => NETWORK_MAINNET,
32+
bitcoin::Network::Testnet => NETWORK_TESTNET,
33+
bitcoin::Network::Testnet4 => NETWORK_TESTNET4,
34+
bitcoin::Network::Signet => unreachable!(),
35+
bitcoin::Network::Regtest => NETWORK_REGTEST,
36+
_ => unreachable!(),
37+
}
38+
}
39+
40+
/// A bitcr Node ID of the format <prefix><network><pub_key>
41+
/// Example: bitcrt039180c169e5f6d7c579cf1cefa37bffd47a2b389c8125601f4068c87bea795943
42+
/// The prefix is bitcr
43+
/// The pub key is a secp256k1 public key
44+
/// The network character can be parsed like this:
45+
/// * m => Mainnet
46+
/// * t => Testnet
47+
/// * T => Testnet4
48+
/// * r => Regtest
49+
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
50+
pub struct NodeId {
51+
pub_key: bitcoin::secp256k1::PublicKey,
52+
network: bitcoin::Network,
53+
}
54+
55+
impl NodeId {
56+
pub fn new(pub_key: bitcoin::secp256k1::PublicKey, network: bitcoin::Network) -> Self {
57+
Self { pub_key, network }
58+
}
59+
60+
pub fn network(&self) -> bitcoin::Network {
61+
self.network
62+
}
63+
64+
pub fn pub_key(&self) -> bitcoin::secp256k1::PublicKey {
65+
self.pub_key
66+
}
67+
68+
pub fn npub(&self) -> nostr::PublicKey {
69+
nostr::PublicKey::from(self.pub_key.x_only_public_key().0)
70+
}
71+
72+
pub fn equals_npub(&self, npub: &nostr::PublicKey) -> bool {
73+
self.npub() == *npub
74+
}
75+
}
76+
77+
impl std::fmt::Display for NodeId {
78+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
write!(
80+
f,
81+
"{}{}{}",
82+
ID_PREFIX,
83+
network_char(&self.network),
84+
self.pub_key
85+
)
86+
}
87+
}
88+
89+
impl FromStr for NodeId {
90+
type Err = ValidationError;
91+
92+
fn from_str(s: &str) -> Result<Self, Self::Err> {
93+
if !s.starts_with(ID_PREFIX) {
94+
return Err(ValidationError::InvalidNodeId);
95+
}
96+
97+
let network = match s.chars().nth(ID_PREFIX.len()) {
98+
None => {
99+
return Err(ValidationError::InvalidNodeId);
100+
}
101+
Some(network_str) => match network_str {
102+
NETWORK_MAINNET => bitcoin::Network::Bitcoin,
103+
NETWORK_TESTNET => bitcoin::Network::Testnet,
104+
NETWORK_TESTNET4 => bitcoin::Network::Testnet4,
105+
NETWORK_REGTEST => bitcoin::Network::Regtest,
106+
_ => {
107+
return Err(ValidationError::InvalidNodeId);
108+
}
109+
},
110+
};
111+
112+
let pub_key_str = &s[ID_PREFIX.len() + 1..];
113+
let pub_key = bitcoin::secp256k1::PublicKey::from_str(pub_key_str)
114+
.map_err(|_| ValidationError::InvalidNodeId)?;
115+
116+
Ok(Self { pub_key, network })
117+
}
118+
}
119+
120+
impl serde::Serialize for NodeId {
121+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
122+
where
123+
S: serde::Serializer,
124+
{
125+
s.collect_str(self)
126+
}
127+
}
128+
129+
impl<'de> serde::Deserialize<'de> for NodeId {
130+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
131+
where
132+
D: serde::Deserializer<'de>,
133+
{
134+
let s = String::deserialize(d)?;
135+
NodeId::from_str(&s).map_err(serde::de::Error::custom)
136+
}
137+
}
138+
139+
impl borsh::BorshSerialize for NodeId {
140+
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
141+
let node_id_str = self.to_string();
142+
borsh::BorshSerialize::serialize(&node_id_str, writer)
143+
}
144+
}
145+
146+
impl borsh::BorshDeserialize for NodeId {
147+
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
148+
let node_id_str: String = borsh::BorshDeserialize::deserialize_reader(reader)?;
149+
NodeId::from_str(&node_id_str)
150+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
151+
}
152+
}
153+
23154
/// Return type of an async function. Can be used to avoid async_trait
24155
pub type BoxedFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
25156

@@ -258,6 +389,10 @@ pub enum ValidationError {
258389
#[error("invalid bill type")]
259390
InvalidBillType,
260391

392+
/// errors stemming from providing an invalid bill id
393+
#[error("invalid bill id")]
394+
InvalidBillId,
395+
261396
/// errors stemming from when the drawee is the payee
262397
#[error("Drawee can't be Payee at the same time")]
263398
DraweeCantBePayee,
@@ -463,6 +598,10 @@ pub enum ValidationError {
463598
#[error("Invalid contact type")]
464599
InvalidContactType,
465600

601+
/// error returned if the node id is not valid
602+
#[error("Invalid node id")]
603+
InvalidNodeId,
604+
466605
/// error returned if the identity type is not valid
467606
#[error("Invalid identity type")]
468607
InvalidIdentityType,

0 commit comments

Comments
 (0)