mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-04 12:45:55 +01:00
cashu: adj. dependencies
* Remove `k256`, `bip32`, `hex`, `log`, `rand` and `itertools` deps * Add `once_cell` and `instant` deps * Downgrade `bitcoin` to `v0.30` and `base64` to `v0.21` * Replace `utils` module with `util` * Remove `utils` from `cashu-sdk` * Some cleanups Closes https://github.com/thesimplekid/cashu-crab/issues/35 Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"crates/cashu",
|
||||
"crates/cashu-sdk",
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod client;
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
pub mod mint;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet;
|
||||
|
||||
pub use bip39::Mnemonic;
|
||||
pub use cashu::{self, *};
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod client;
|
||||
#[cfg(feature = "mint")]
|
||||
pub mod mint;
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet;
|
||||
|
||||
@@ -18,9 +18,9 @@ pub struct MemoryLocalStore {
|
||||
keysets: Arc<Mutex<HashMap<Id, KeySet>>>,
|
||||
mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
|
||||
melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
|
||||
pending_proofs: Arc<Mutex<HashMap<Vec<u8>, Proof>>>,
|
||||
spent_proofs: Arc<Mutex<HashMap<Vec<u8>, Proof>>>,
|
||||
blinded_signatures: Arc<Mutex<HashMap<Box<[u8]>, BlindSignature>>>,
|
||||
pending_proofs: Arc<Mutex<HashMap<[u8; 33], Proof>>>,
|
||||
spent_proofs: Arc<Mutex<HashMap<[u8; 33], Proof>>>,
|
||||
blinded_signatures: Arc<Mutex<HashMap<[u8; 33], BlindSignature>>>,
|
||||
}
|
||||
|
||||
impl MemoryLocalStore {
|
||||
@@ -33,7 +33,7 @@ impl MemoryLocalStore {
|
||||
melt_quotes: Vec<MeltQuote>,
|
||||
pending_proofs: Proofs,
|
||||
spent_proofs: Proofs,
|
||||
blinded_signatures: HashMap<Box<[u8]>, BlindSignature>,
|
||||
blinded_signatures: HashMap<[u8; 33], BlindSignature>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
mint_info: Arc::new(Mutex::new(mint_info)),
|
||||
@@ -48,29 +48,13 @@ impl MemoryLocalStore {
|
||||
pending_proofs: Arc::new(Mutex::new(
|
||||
pending_proofs
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
(
|
||||
hash_to_curve(&p.secret.to_bytes())
|
||||
.unwrap()
|
||||
.to_sec1_bytes()
|
||||
.to_vec(),
|
||||
p,
|
||||
)
|
||||
})
|
||||
.map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p))
|
||||
.collect(),
|
||||
)),
|
||||
spent_proofs: Arc::new(Mutex::new(
|
||||
spent_proofs
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
(
|
||||
hash_to_curve(&p.secret.to_bytes())
|
||||
.unwrap()
|
||||
.to_sec1_bytes()
|
||||
.to_vec(),
|
||||
p,
|
||||
)
|
||||
})
|
||||
.map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p))
|
||||
.collect(),
|
||||
)),
|
||||
blinded_signatures: Arc::new(Mutex::new(blinded_signatures)),
|
||||
@@ -163,7 +147,7 @@ impl LocalStore for MemoryLocalStore {
|
||||
self.spent_proofs
|
||||
.lock()
|
||||
.await
|
||||
.insert(secret_point.to_sec1_bytes().to_vec(), proof);
|
||||
.insert(secret_point.to_bytes(), proof);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -172,26 +156,19 @@ impl LocalStore for MemoryLocalStore {
|
||||
.spent_proofs
|
||||
.lock()
|
||||
.await
|
||||
.get(&hash_to_curve(&secret.to_bytes())?.to_sec1_bytes().to_vec())
|
||||
.get(&hash_to_curve(&secret.to_bytes())?.to_bytes())
|
||||
.cloned())
|
||||
}
|
||||
|
||||
async fn get_spent_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Error> {
|
||||
Ok(self
|
||||
.spent_proofs
|
||||
.lock()
|
||||
.await
|
||||
.get(&y.to_bytes().to_vec())
|
||||
.cloned())
|
||||
Ok(self.spent_proofs.lock().await.get(&y.to_bytes()).cloned())
|
||||
}
|
||||
|
||||
async fn add_pending_proof(&self, proof: Proof) -> Result<(), Error> {
|
||||
self.pending_proofs.lock().await.insert(
|
||||
hash_to_curve(&proof.secret.to_bytes())?
|
||||
.to_sec1_bytes()
|
||||
.to_vec(),
|
||||
proof,
|
||||
);
|
||||
self.pending_proofs
|
||||
.lock()
|
||||
.await
|
||||
.insert(hash_to_curve(&proof.secret.to_bytes())?.to_bytes(), proof);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -201,17 +178,12 @@ impl LocalStore for MemoryLocalStore {
|
||||
.pending_proofs
|
||||
.lock()
|
||||
.await
|
||||
.get(&secret_point.to_sec1_bytes().to_vec())
|
||||
.get(&secret_point.to_bytes())
|
||||
.cloned())
|
||||
}
|
||||
|
||||
async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Error> {
|
||||
Ok(self
|
||||
.pending_proofs
|
||||
.lock()
|
||||
.await
|
||||
.get(&y.to_bytes().to_vec())
|
||||
.cloned())
|
||||
Ok(self.pending_proofs.lock().await.get(&y.to_bytes()).cloned())
|
||||
}
|
||||
|
||||
async fn remove_pending_proof(&self, secret: &Secret) -> Result<(), Error> {
|
||||
@@ -219,7 +191,7 @@ impl LocalStore for MemoryLocalStore {
|
||||
self.pending_proofs
|
||||
.lock()
|
||||
.await
|
||||
.remove(&secret_point.to_sec1_bytes().to_vec());
|
||||
.remove(&secret_point.to_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,13 @@ const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("
|
||||
const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets");
|
||||
const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
|
||||
const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
|
||||
const PENDING_PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("pending_proofs");
|
||||
const SPENT_PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("spent_proofs");
|
||||
const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> =
|
||||
TableDefinition::new("pending_proofs");
|
||||
const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs");
|
||||
const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
|
||||
// Key is hex blinded_message B_ value is blinded_signature
|
||||
const BLINDED_SIGNATURES: TableDefinition<&[u8], &str> = TableDefinition::new("blinded_signatures");
|
||||
const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> =
|
||||
TableDefinition::new("blinded_signatures");
|
||||
|
||||
const DATABASE_VERSION: u64 = 0;
|
||||
|
||||
@@ -307,11 +309,8 @@ impl LocalStore for RedbLocalStore {
|
||||
|
||||
{
|
||||
let mut table = write_txn.open_table(SPENT_PROOFS_TABLE)?;
|
||||
let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?.into();
|
||||
table.insert(
|
||||
y.to_bytes().as_ref(),
|
||||
serde_json::to_string(&proof)?.as_str(),
|
||||
)?;
|
||||
let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?;
|
||||
table.insert(y.to_bytes(), serde_json::to_string(&proof)?.as_str())?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
debug!("Added spend secret: {}", proof.secret.to_string());
|
||||
@@ -324,7 +323,7 @@ impl LocalStore for RedbLocalStore {
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(SPENT_PROOFS_TABLE)?;
|
||||
|
||||
let proof = table.get(y.to_bytes().as_ref())?;
|
||||
let proof = table.get(y.to_bytes())?;
|
||||
|
||||
if let Some(proof) = proof {
|
||||
Ok(serde_json::from_str(proof.value())?)
|
||||
@@ -338,9 +337,9 @@ impl LocalStore for RedbLocalStore {
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(SPENT_PROOFS_TABLE)?;
|
||||
|
||||
let y: PublicKey = hash_to_curve(&secret.to_bytes())?.into();
|
||||
let y: PublicKey = hash_to_curve(&secret.to_bytes())?;
|
||||
|
||||
let proof = table.get(y.to_bytes().as_ref())?;
|
||||
let proof = table.get(y.to_bytes())?;
|
||||
|
||||
debug!("Checking secret: {}", secret.to_string());
|
||||
|
||||
@@ -359,9 +358,7 @@ impl LocalStore for RedbLocalStore {
|
||||
{
|
||||
let mut table = write_txn.open_table(PENDING_PROOFS_TABLE)?;
|
||||
table.insert(
|
||||
hash_to_curve(&proof.secret.to_bytes())?
|
||||
.to_sec1_bytes()
|
||||
.as_ref(),
|
||||
hash_to_curve(&proof.secret.to_bytes())?.to_bytes(),
|
||||
serde_json::to_string(&proof)?.as_str(),
|
||||
)?;
|
||||
}
|
||||
@@ -375,7 +372,7 @@ impl LocalStore for RedbLocalStore {
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(PENDING_PROOFS_TABLE)?;
|
||||
|
||||
let proof = table.get(y.to_bytes().as_ref())?;
|
||||
let proof = table.get(y.to_bytes())?;
|
||||
|
||||
if let Some(proof) = proof {
|
||||
Ok(serde_json::from_str(proof.value())?)
|
||||
@@ -391,7 +388,7 @@ impl LocalStore for RedbLocalStore {
|
||||
|
||||
let secret_hash = hash_to_curve(&secret.to_bytes())?;
|
||||
|
||||
let proof = table.get(secret_hash.to_sec1_bytes().as_ref())?;
|
||||
let proof = table.get(secret_hash.to_bytes())?;
|
||||
|
||||
if let Some(proof) = proof {
|
||||
Ok(serde_json::from_str(proof.value())?)
|
||||
@@ -408,7 +405,7 @@ impl LocalStore for RedbLocalStore {
|
||||
{
|
||||
let mut table = write_txn.open_table(PENDING_PROOFS_TABLE)?;
|
||||
let secret_hash = hash_to_curve(&secret.to_bytes())?;
|
||||
table.remove(secret_hash.to_sec1_bytes().as_ref())?;
|
||||
table.remove(secret_hash.to_bytes())?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
|
||||
@@ -426,7 +423,7 @@ impl LocalStore for RedbLocalStore {
|
||||
{
|
||||
let mut table = write_txn.open_table(BLINDED_SIGNATURES)?;
|
||||
table.insert(
|
||||
blinded_message.to_bytes().as_ref(),
|
||||
blinded_message.to_bytes(),
|
||||
serde_json::to_string(&blinded_signature)?.as_str(),
|
||||
)?;
|
||||
}
|
||||
@@ -444,7 +441,7 @@ impl LocalStore for RedbLocalStore {
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(BLINDED_SIGNATURES)?;
|
||||
|
||||
if let Some(blinded_signature) = table.get(blinded_message.to_bytes().as_ref())? {
|
||||
if let Some(blinded_signature) = table.get(blinded_message.to_bytes())? {
|
||||
return Ok(serde_json::from_str(blinded_signature.value())?);
|
||||
}
|
||||
|
||||
@@ -462,7 +459,7 @@ impl LocalStore for RedbLocalStore {
|
||||
let mut signatures = Vec::with_capacity(blinded_messages.len());
|
||||
|
||||
for blinded_message in blinded_messages {
|
||||
if let Some(blinded_signature) = table.get(blinded_message.to_bytes().as_ref())? {
|
||||
if let Some(blinded_signature) = table.get(blinded_message.to_bytes())? {
|
||||
signatures.push(Some(serde_json::from_str(blinded_signature.value())?))
|
||||
} else {
|
||||
signatures.push(None);
|
||||
|
||||
@@ -355,7 +355,7 @@ impl Mint {
|
||||
return Err(Error::AmountKey);
|
||||
};
|
||||
|
||||
let c = sign_message(key_pair.secret_key.clone().into(), b.clone().into())?;
|
||||
let c = sign_message(&key_pair.secret_key, b)?;
|
||||
|
||||
let blinded_signature;
|
||||
#[cfg(not(feature = "nut12"))]
|
||||
@@ -369,9 +369,9 @@ impl Mint {
|
||||
|
||||
#[cfg(feature = "nut12")]
|
||||
{
|
||||
blinded_signature = BlindSignature::new_dleq(
|
||||
blinded_signature = BlindSignature::new(
|
||||
*amount,
|
||||
c.into(),
|
||||
c,
|
||||
keyset.id,
|
||||
&blinded_message.b,
|
||||
key_pair.secret_key.clone(),
|
||||
@@ -407,11 +407,11 @@ impl Mint {
|
||||
|
||||
let proof_count = swap_request.inputs.len();
|
||||
|
||||
let secrets: HashSet<Vec<u8>> = swap_request
|
||||
let secrets: HashSet<[u8; 33]> = swap_request
|
||||
.inputs
|
||||
.iter()
|
||||
.flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
|
||||
.map(|p| p.to_sec1_bytes().to_vec())
|
||||
.map(|p| p.to_bytes())
|
||||
.collect();
|
||||
|
||||
// Check that there are no duplicate proofs in request
|
||||
@@ -524,7 +524,7 @@ impl Mint {
|
||||
}
|
||||
}
|
||||
|
||||
let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?.into();
|
||||
let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?;
|
||||
|
||||
if self.localstore.get_spent_proof_by_y(&y).await?.is_some() {
|
||||
return Err(Error::TokenSpent);
|
||||
@@ -544,11 +544,7 @@ impl Mint {
|
||||
return Err(Error::AmountKey);
|
||||
};
|
||||
|
||||
verify_message(
|
||||
keypair.secret_key.clone().into(),
|
||||
proof.c.clone().into(),
|
||||
&proof.secret.to_bytes(),
|
||||
)?;
|
||||
verify_message(&keypair.secret_key, proof.c, proof.secret.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -570,7 +566,7 @@ impl Mint {
|
||||
};
|
||||
|
||||
states.push(ProofState {
|
||||
y: y.clone(),
|
||||
y: *y,
|
||||
state,
|
||||
witness: None,
|
||||
})
|
||||
@@ -643,11 +639,11 @@ impl Mint {
|
||||
return Err(Error::MultipleUnits);
|
||||
}
|
||||
|
||||
let secrets: HashSet<Vec<u8>> = melt_request
|
||||
let secrets: HashSet<[u8; 33]> = melt_request
|
||||
.inputs
|
||||
.iter()
|
||||
.flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
|
||||
.map(|p| p.to_sec1_bytes().to_vec())
|
||||
.map(|p| p.to_bytes())
|
||||
.collect();
|
||||
|
||||
// Ensure proofs are unique and not being double spent
|
||||
@@ -761,7 +757,7 @@ impl Mint {
|
||||
let mut outputs = Vec::with_capacity(output_len);
|
||||
let mut signatures = Vec::with_capacity(output_len);
|
||||
|
||||
let blinded_message: Vec<PublicKey> = request.outputs.iter().map(|b| b.b.clone()).collect();
|
||||
let blinded_message: Vec<PublicKey> = request.outputs.iter().map(|b| b.b).collect();
|
||||
|
||||
let blinded_signatures = self
|
||||
.localstore
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub fn unix_time() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|x| x.as_secs())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
@@ -20,13 +20,13 @@ use cashu::nuts::{
|
||||
};
|
||||
use cashu::types::{MeltQuote, Melted, MintQuote};
|
||||
use cashu::url::UncheckedUrl;
|
||||
use cashu::util::unix_time;
|
||||
use cashu::{Amount, Bolt11Invoice};
|
||||
use localstore::LocalStore;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::utils::unix_time;
|
||||
|
||||
pub mod localstore;
|
||||
|
||||
@@ -200,13 +200,10 @@ impl Wallet {
|
||||
.post_check_state(
|
||||
mint_url.try_into()?,
|
||||
proofs
|
||||
.clone()
|
||||
.into_iter()
|
||||
// Find Y for the secret
|
||||
.flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
|
||||
.map(|y| y.into())
|
||||
.collect::<Vec<PublicKey>>()
|
||||
.clone(),
|
||||
.flat_map(|p| hash_to_curve(p.secret.as_bytes()))
|
||||
.collect::<Vec<PublicKey>>(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -369,7 +366,7 @@ impl Wallet {
|
||||
for (sig, premint) in mint_res.signatures.iter().zip(&premint_secrets.secrets) {
|
||||
let keys = self.get_keyset_keys(&mint_url, sig.keyset_id).await?;
|
||||
let key = keys.amount_key(sig.amount).ok_or(Error::UnknownKey)?;
|
||||
match sig.verify_dleq(&key, &premint.blinded_message.b) {
|
||||
match sig.verify_dleq(key, premint.blinded_message.b) {
|
||||
Ok(_) => (),
|
||||
Err(cashu::nuts::nut12::Error::MissingDleqProof) => (),
|
||||
Err(_) => return Err(Error::CouldNotVerifyDleq),
|
||||
@@ -420,7 +417,7 @@ impl Wallet {
|
||||
for proof in proofs {
|
||||
let keys = self.get_keyset_keys(mint_url, proof.keyset_id).await?;
|
||||
let key = keys.amount_key(proof.amount).ok_or(Error::UnknownKey)?;
|
||||
match proof.verify_dleq(&key) {
|
||||
match proof.verify_dleq(key) {
|
||||
Ok(_) => continue,
|
||||
Err(cashu::nuts::nut12::Error::MissingDleqProof) => continue,
|
||||
Err(_) => return Err(Error::CouldNotVerifyDleq),
|
||||
@@ -581,7 +578,7 @@ impl Wallet {
|
||||
.await?
|
||||
.ok_or(Error::UnknownKey)?;
|
||||
let key = keys.amount_key(promise.amount).ok_or(Error::UnknownKey)?;
|
||||
match promise.verify_dleq(&key, &premint.blinded_message.b) {
|
||||
match promise.verify_dleq(key, premint.blinded_message.b) {
|
||||
Ok(_) => (),
|
||||
Err(cashu::nuts::nut12::Error::MissingDleqProof) => (),
|
||||
Err(_) => return Err(Error::CouldNotVerifyDleq),
|
||||
@@ -597,9 +594,7 @@ impl Wallet {
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
let blinded_c = promise.c.clone();
|
||||
|
||||
let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap();
|
||||
let unblinded_sig = unblind_message(&promise.c, &premint.r, &a).unwrap();
|
||||
|
||||
let count = proof_count.get(&promise.keyset_id).unwrap_or(&0);
|
||||
proof_count.insert(promise.keyset_id, count + 1);
|
||||
@@ -1012,7 +1007,7 @@ impl Wallet {
|
||||
{
|
||||
let keys = self.localstore.get_keys(&proof.keyset_id).await?.unwrap();
|
||||
let key = keys.amount_key(proof.amount).unwrap();
|
||||
proof.verify_dleq(&key).unwrap();
|
||||
proof.verify_dleq(key).unwrap();
|
||||
}
|
||||
|
||||
if let Ok(secret) =
|
||||
@@ -1309,7 +1304,7 @@ impl Wallet {
|
||||
.ok_or(Error::UnknownKey)?;
|
||||
|
||||
proof
|
||||
.verify_dleq(&mint_pubkey)
|
||||
.verify_dleq(mint_pubkey)
|
||||
.map_err(|_| Error::CouldNotVerifyDleq)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,29 +22,22 @@ nut09 = []
|
||||
nut10 = []
|
||||
nut11 = ["nut10"]
|
||||
nut12 = []
|
||||
nut13 = ["dep:bip39", "dep:bip32", "nut09"]
|
||||
|
||||
nut13 = ["dep:bip39", "nut09"]
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.0"
|
||||
bitcoin = { version = "0.31.0", features=["serde", "rand"] }
|
||||
bip39 = { version = "2.0.0", optional = true }
|
||||
bip32 = { version = "0.5.1", optional = true }
|
||||
hex = "0.4.3"
|
||||
k256 = { version = "0.13.1", features=["arithmetic", "serde", "schnorr"] }
|
||||
lightning-invoice = { version = "0.29.0", features=["serde"] }
|
||||
log = "0.4.2"
|
||||
rand = "0.8.5"
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = "3.4.0"
|
||||
url = { workspace = true }
|
||||
itertools = "0.12.0"
|
||||
thiserror = { workspace = true }
|
||||
uuid = { version = "1.6.1", features = ["v4"] }
|
||||
base64 = "0.21" # bitcoin uses v0.21 (optional dep)
|
||||
bip39 = { version = "2.0", optional = true }
|
||||
bitcoin = { version = "0.30", features = ["serde", "rand", "rand-std"] } # lightning-invoice uses v0.30
|
||||
lightning-invoice = { version = "0.29", features = ["serde"] }
|
||||
once_cell = "1.19"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_with = "3.4"
|
||||
url.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid = { version = "1.6", features = ["v4"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# tokio = {version = "1.27.0", features = ["rt", "macros"] }
|
||||
getrandom.workspace = true
|
||||
instant = { version = "0.1", features = [ "wasm-bindgen", "inaccurate" ] }
|
||||
|
||||
@@ -1,36 +1,39 @@
|
||||
//! Diffie-Hellmann key exchange
|
||||
|
||||
use bitcoin::hashes::{sha256, Hash};
|
||||
use k256::elliptic_curve::sec1::ToEncodedPoint;
|
||||
#[cfg(feature = "mint")]
|
||||
pub use mint::{sign_message, verify_message};
|
||||
#[cfg(feature = "wallet")]
|
||||
pub use wallet::{blind_message, construct_proofs, unblind_message};
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::error::Error;
|
||||
use bitcoin::hashes::sha256::Hash as Sha256Hash;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::secp256k1::{Parity, PublicKey as NormalizedPublicKey, Scalar, XOnlyPublicKey};
|
||||
|
||||
use crate::error::{self, Error};
|
||||
use crate::nuts::nut01::{PublicKey, SecretKey};
|
||||
#[cfg(feature = "nut12")]
|
||||
use crate::nuts::nut12::ProofDleq;
|
||||
use crate::nuts::{BlindSignature, Keys, Proof, Proofs};
|
||||
use crate::secret::Secret;
|
||||
use crate::util::hex;
|
||||
use crate::SECP256K1;
|
||||
|
||||
const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_";
|
||||
|
||||
pub fn hash_to_curve(message: &[u8]) -> Result<k256::PublicKey, Error> {
|
||||
let msg_to_hash = [DOMAIN_SEPARATOR, message].concat();
|
||||
pub fn hash_to_curve(message: &[u8]) -> Result<PublicKey, Error> {
|
||||
let msg_to_hash: Vec<u8> = [DOMAIN_SEPARATOR, message].concat();
|
||||
|
||||
let msg_hash = sha256::Hash::hash(&msg_to_hash).to_byte_array();
|
||||
let msg_hash: [u8; 32] = Sha256Hash::hash(&msg_to_hash).to_byte_array();
|
||||
|
||||
let mut counter = 0;
|
||||
let mut counter: u32 = 0;
|
||||
while counter < 2_u32.pow(16) {
|
||||
let mut bytes_to_hash = Vec::with_capacity(36);
|
||||
let mut bytes_to_hash: Vec<u8> = Vec::with_capacity(36);
|
||||
bytes_to_hash.extend_from_slice(&msg_hash);
|
||||
bytes_to_hash.extend_from_slice(&counter.to_le_bytes());
|
||||
let hash: [u8; 32] = Sha256Hash::hash(&bytes_to_hash).to_byte_array();
|
||||
|
||||
let hash = sha256::Hash::hash(&bytes_to_hash);
|
||||
match k256::PublicKey::from_sec1_bytes(
|
||||
&[0x02u8]
|
||||
.iter()
|
||||
.chain(&hash.to_byte_array())
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>(),
|
||||
) {
|
||||
Ok(pubkey) => return Ok(pubkey),
|
||||
// Try to parse public key
|
||||
match XOnlyPublicKey::from_slice(&hash) {
|
||||
Ok(pk) => {
|
||||
return Ok(NormalizedPublicKey::from_x_only_public_key(pk, Parity::Even).into())
|
||||
}
|
||||
Err(_) => {
|
||||
counter += 1;
|
||||
}
|
||||
@@ -40,215 +43,182 @@ pub fn hash_to_curve(message: &[u8]) -> Result<k256::PublicKey, Error> {
|
||||
Err(Error::NoValidPoint)
|
||||
}
|
||||
|
||||
pub fn hash_e(pubkeys: Vec<k256::PublicKey>) -> Vec<u8> {
|
||||
let mut e = "".to_string();
|
||||
pub fn hash_e<I>(public_keys: I) -> [u8; 32]
|
||||
where
|
||||
I: IntoIterator<Item = PublicKey>,
|
||||
{
|
||||
let mut e: String = String::new();
|
||||
|
||||
for pubkey in pubkeys {
|
||||
let uncompressed_point = pubkey.to_encoded_point(false).to_bytes();
|
||||
|
||||
e.push_str(&hex::encode(uncompressed_point));
|
||||
for public_key in public_keys.into_iter() {
|
||||
let uncompressed: [u8; 65] = public_key.to_uncompressed_bytes();
|
||||
e.push_str(&hex::encode(uncompressed));
|
||||
}
|
||||
|
||||
sha256::Hash::hash(e.as_bytes()).to_byte_array().to_vec()
|
||||
Sha256Hash::hash(e.as_bytes()).to_byte_array()
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
mod wallet {
|
||||
use std::ops::Mul;
|
||||
/// Blind Message
|
||||
///
|
||||
/// `B_ = Y + rG`
|
||||
pub fn blind_message(
|
||||
secret: &[u8],
|
||||
blinding_factor: Option<SecretKey>,
|
||||
) -> Result<(PublicKey, SecretKey), error::wallet::Error> {
|
||||
let y: PublicKey = hash_to_curve(secret)?;
|
||||
let r: SecretKey = blinding_factor.unwrap_or_else(SecretKey::generate);
|
||||
Ok((y.combine(&r.public_key())?.into(), r))
|
||||
}
|
||||
|
||||
use k256::{ProjectivePoint, Scalar, SecretKey};
|
||||
/// Unblind Message
|
||||
///
|
||||
/// `C_ - rK`
|
||||
pub fn unblind_message(
|
||||
// C_
|
||||
blinded_key: &PublicKey,
|
||||
r: &SecretKey,
|
||||
// K
|
||||
mint_pubkey: &PublicKey,
|
||||
) -> Result<PublicKey, error::wallet::Error> {
|
||||
let r: Scalar = Scalar::from(r.deref().to_owned());
|
||||
|
||||
use super::hash_to_curve;
|
||||
use crate::error;
|
||||
use crate::nuts::{BlindSignature, Keys, Proof, Proofs, PublicKey, *};
|
||||
use crate::secret::Secret;
|
||||
// a = r * K
|
||||
let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
|
||||
|
||||
/// Blind Message Alice Step one
|
||||
pub fn blind_message(
|
||||
secret: &[u8],
|
||||
blinding_factor: Option<SecretKey>,
|
||||
) -> Result<(PublicKey, SecretKey), error::wallet::Error> {
|
||||
let y = hash_to_curve(secret)?;
|
||||
// C_ - a
|
||||
let a: PublicKey = a.negate(&SECP256K1).into();
|
||||
Ok(blinded_key.combine(&a)?.into()) // C_ + (-a)
|
||||
}
|
||||
|
||||
let r: SecretKey = match blinding_factor {
|
||||
Some(sec_key) => sec_key,
|
||||
None => SecretKey::random(&mut rand::thread_rng()),
|
||||
};
|
||||
|
||||
let b = ProjectivePoint::from(y) + ProjectivePoint::from(&r.public_key());
|
||||
|
||||
Ok((k256::PublicKey::try_from(b)?.into(), r))
|
||||
}
|
||||
|
||||
/// Unblind Message (Alice Step 3)
|
||||
pub fn unblind_message(
|
||||
// C_
|
||||
blinded_key: PublicKey,
|
||||
r: SecretKey,
|
||||
// A
|
||||
mint_pubkey: PublicKey,
|
||||
) -> Result<PublicKey, error::wallet::Error> {
|
||||
// C
|
||||
// Unblinded message
|
||||
let c = ProjectivePoint::from(Into::<k256::PublicKey>::into(blinded_key).as_affine())
|
||||
- Into::<k256::PublicKey>::into(mint_pubkey)
|
||||
.as_affine()
|
||||
.mul(Scalar::from(r.as_scalar_primitive()));
|
||||
|
||||
Ok(k256::PublicKey::try_from(c)?.into())
|
||||
}
|
||||
|
||||
/// Construct Proof
|
||||
pub fn construct_proofs(
|
||||
promises: Vec<BlindSignature>,
|
||||
rs: Vec<nut01::SecretKey>,
|
||||
secrets: Vec<Secret>,
|
||||
keys: &Keys,
|
||||
) -> Result<Proofs, error::wallet::Error> {
|
||||
let mut proofs = vec![];
|
||||
for ((blinded_signature, r), secret) in promises.into_iter().zip(rs).zip(secrets) {
|
||||
let blinded_c = blinded_signature.c;
|
||||
let a: PublicKey = keys
|
||||
.amount_key(blinded_signature.amount)
|
||||
/// Construct Proof
|
||||
pub fn construct_proofs(
|
||||
promises: Vec<BlindSignature>,
|
||||
rs: Vec<SecretKey>,
|
||||
secrets: Vec<Secret>,
|
||||
keys: &Keys,
|
||||
) -> Result<Proofs, error::wallet::Error> {
|
||||
let mut proofs = vec![];
|
||||
for ((blinded_signature, r), secret) in promises.into_iter().zip(rs).zip(secrets) {
|
||||
let blinded_c: PublicKey = blinded_signature.c;
|
||||
let a: PublicKey =
|
||||
keys.amount_key(blinded_signature.amount)
|
||||
.ok_or(error::wallet::Error::CustomError(
|
||||
"Could not get proofs".to_string(),
|
||||
))?
|
||||
.to_owned();
|
||||
))?;
|
||||
|
||||
let unblinded_signature = unblind_message(blinded_c, r.clone().into(), a)?;
|
||||
let unblinded_signature: PublicKey = unblind_message(&blinded_c, &r, &a)?;
|
||||
|
||||
let proof;
|
||||
let proof;
|
||||
|
||||
#[cfg(not(feature = "nut12"))]
|
||||
{
|
||||
proof = Proof {
|
||||
amount: blinded_signature.amount,
|
||||
keyset_id: blinded_signature.keyset_id,
|
||||
secret,
|
||||
c: unblinded_signature,
|
||||
#[cfg(feature = "nut11")]
|
||||
witness: None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "nut12")]
|
||||
{
|
||||
let dleq = if let Some(dleq) = blinded_signature.dleq {
|
||||
Some(ProofDleq {
|
||||
e: dleq.e,
|
||||
s: dleq.s,
|
||||
r,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
proof = Proof {
|
||||
amount: blinded_signature.amount,
|
||||
keyset_id: blinded_signature.keyset_id,
|
||||
secret,
|
||||
c: unblinded_signature,
|
||||
#[cfg(feature = "nut11")]
|
||||
witness: None,
|
||||
dleq,
|
||||
};
|
||||
}
|
||||
|
||||
proofs.push(proof);
|
||||
#[cfg(not(feature = "nut12"))]
|
||||
{
|
||||
proof = Proof {
|
||||
amount: blinded_signature.amount,
|
||||
keyset_id: blinded_signature.keyset_id,
|
||||
secret,
|
||||
c: unblinded_signature,
|
||||
#[cfg(feature = "nut11")]
|
||||
witness: None,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(proofs)
|
||||
#[cfg(feature = "nut12")]
|
||||
{
|
||||
let dleq = if let Some(dleq) = blinded_signature.dleq {
|
||||
Some(ProofDleq {
|
||||
e: dleq.e,
|
||||
s: dleq.s,
|
||||
r,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
proof = Proof {
|
||||
amount: blinded_signature.amount,
|
||||
keyset_id: blinded_signature.keyset_id,
|
||||
secret,
|
||||
c: unblinded_signature,
|
||||
#[cfg(feature = "nut11")]
|
||||
witness: None,
|
||||
dleq,
|
||||
};
|
||||
}
|
||||
|
||||
proofs.push(proof);
|
||||
}
|
||||
|
||||
Ok(proofs)
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
mod mint {
|
||||
use std::ops::Mul;
|
||||
/// Sign Blinded Message
|
||||
///
|
||||
/// `C_ = k * B_`, where:
|
||||
/// * `k` is the private key of mint (one for each amount)
|
||||
/// * `B_` is the blinded message
|
||||
#[inline]
|
||||
pub fn sign_message(
|
||||
k: &SecretKey,
|
||||
blinded_message: &PublicKey,
|
||||
) -> Result<PublicKey, error::mint::Error> {
|
||||
let k: Scalar = Scalar::from(k.deref().to_owned());
|
||||
Ok(blinded_message.mul_tweak(&SECP256K1, &k)?.into())
|
||||
}
|
||||
|
||||
use k256::{Scalar, SecretKey};
|
||||
use log::warn;
|
||||
/// Verify Message
|
||||
pub fn verify_message(
|
||||
a: &SecretKey,
|
||||
unblinded_message: PublicKey,
|
||||
msg: &[u8],
|
||||
) -> Result<(), error::mint::Error> {
|
||||
// Y
|
||||
let y: PublicKey = hash_to_curve(msg)?;
|
||||
|
||||
use super::hash_to_curve;
|
||||
use crate::error;
|
||||
// Compute the expected unblinded message
|
||||
let expected_unblinded_message: PublicKey = y.combine(&a.public_key())?.into();
|
||||
|
||||
/// Sign Blinded Message (Step2 bob)
|
||||
pub fn sign_message(
|
||||
a: SecretKey,
|
||||
blinded_message: k256::PublicKey,
|
||||
) -> Result<k256::PublicKey, error::mint::Error> {
|
||||
Ok(k256::PublicKey::try_from(
|
||||
blinded_message
|
||||
.as_affine()
|
||||
.mul(Scalar::from(a.as_scalar_primitive())),
|
||||
)?)
|
||||
// Compare the unblinded_message with the expected value
|
||||
if unblinded_message == expected_unblinded_message {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Verify Message
|
||||
pub fn verify_message(
|
||||
a: SecretKey,
|
||||
unblinded_message: k256::PublicKey,
|
||||
msg: &[u8],
|
||||
) -> Result<(), error::mint::Error> {
|
||||
// Y
|
||||
let y = hash_to_curve(msg)?;
|
||||
|
||||
if unblinded_message
|
||||
== k256::PublicKey::try_from(*y.as_affine() * Scalar::from(a.as_scalar_primitive()))?
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
warn!("Message not verifed");
|
||||
|
||||
Err(error::mint::Error::TokenNotVerifed)
|
||||
}
|
||||
Err(error::mint::Error::TokenNotVerifed)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use hex::decode;
|
||||
use k256::elliptic_curve::scalar::ScalarPrimitive;
|
||||
use core::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
use crate::nuts::PublicKey;
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_curve() {
|
||||
let secret = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
let sec_hex = decode(secret).unwrap();
|
||||
let sec_hex = hex::decode(secret).unwrap();
|
||||
|
||||
let y = hash_to_curve(&sec_hex).unwrap();
|
||||
let expected_y = k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode("024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725")
|
||||
.unwrap(),
|
||||
let expected_y = PublicKey::from_hex(
|
||||
"024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725",
|
||||
)
|
||||
.unwrap();
|
||||
println!("{}", hex::encode(y.to_sec1_bytes()));
|
||||
assert_eq!(y, expected_y);
|
||||
|
||||
let secret = "0000000000000000000000000000000000000000000000000000000000000001";
|
||||
let sec_hex = decode(secret).unwrap();
|
||||
let sec_hex = hex::decode(secret).unwrap();
|
||||
let y = hash_to_curve(&sec_hex).unwrap();
|
||||
let expected_y = k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode("022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf")
|
||||
.unwrap(),
|
||||
let expected_y = PublicKey::from_hex(
|
||||
"022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf",
|
||||
)
|
||||
.unwrap();
|
||||
println!("{}", hex::encode(y.to_sec1_bytes()));
|
||||
assert_eq!(y, expected_y);
|
||||
// Note that this message will take a few iterations of the loop before finding
|
||||
// a valid point
|
||||
let secret = "0000000000000000000000000000000000000000000000000000000000000002";
|
||||
let sec_hex = decode(secret).unwrap();
|
||||
let sec_hex = hex::decode(secret).unwrap();
|
||||
let y = hash_to_curve(&sec_hex).unwrap();
|
||||
let expected_y = k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode("026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f")
|
||||
.unwrap(),
|
||||
let expected_y = PublicKey::from_hex(
|
||||
"026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f",
|
||||
)
|
||||
.unwrap();
|
||||
println!("{}", hex::encode(y.to_sec1_bytes()));
|
||||
assert_eq!(y, expected_y);
|
||||
}
|
||||
|
||||
@@ -283,178 +253,138 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
mod wallet_tests {
|
||||
#[test]
|
||||
fn test_blind_message() {
|
||||
let message =
|
||||
hex::decode("d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6")
|
||||
.unwrap();
|
||||
let sec: SecretKey =
|
||||
SecretKey::from_hex("99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a")
|
||||
.unwrap();
|
||||
|
||||
use k256::SecretKey;
|
||||
let (b, r) = blind_message(&message, Some(sec.clone())).unwrap();
|
||||
|
||||
use super::*;
|
||||
use crate::nuts::PublicKey;
|
||||
|
||||
#[test]
|
||||
fn test_blind_message() {
|
||||
let message = "d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6";
|
||||
let sec: crate::nuts::SecretKey = crate::nuts::SecretKey::from_hex(
|
||||
"99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a",
|
||||
assert_eq!(sec, r);
|
||||
assert_eq!(
|
||||
b,
|
||||
PublicKey::from_hex(
|
||||
"033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d"
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let (b, r) =
|
||||
blind_message(&hex::decode(message).unwrap(), Some(sec.clone().into())).unwrap();
|
||||
let message =
|
||||
hex::decode("f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60")
|
||||
.unwrap();
|
||||
let sec: SecretKey =
|
||||
SecretKey::from_hex("f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(sec, r.into());
|
||||
let (b, r) = blind_message(&message, Some(sec.clone())).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
b.to_string(),
|
||||
PublicKey::from(
|
||||
k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode(
|
||||
"033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
|
||||
let message = "f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60";
|
||||
let sec: crate::nuts::SecretKey = crate::nuts::SecretKey::from_hex(
|
||||
"f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50",
|
||||
assert_eq!(sec, r);
|
||||
assert_eq!(
|
||||
b,
|
||||
PublicKey::from_hex(
|
||||
"029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (b, r) =
|
||||
blind_message(&hex::decode(message).unwrap(), Some(sec.clone().into())).unwrap();
|
||||
|
||||
assert_eq!(sec, r.into());
|
||||
|
||||
assert_eq!(
|
||||
b.to_string(),
|
||||
PublicKey::from(
|
||||
k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode(
|
||||
"029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unblind_message() {
|
||||
let blinded_key = k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2")
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let r = SecretKey::new(ScalarPrimitive::ONE);
|
||||
let a = k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode("020000000000000000000000000000000000000000000000000000000000000001")
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let unblinded = unblind_message(blinded_key.into(), r, a.into()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Into::<PublicKey>::into(
|
||||
k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode(
|
||||
"03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
unblinded
|
||||
);
|
||||
}
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
mod mint_test {
|
||||
#[test]
|
||||
fn test_unblind_message() {
|
||||
let blinded_key = PublicKey::from_hex(
|
||||
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
use k256::SecretKey;
|
||||
let r =
|
||||
SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
.unwrap();
|
||||
let a = PublicKey::from_hex(
|
||||
"020000000000000000000000000000000000000000000000000000000000000001",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
use super::{hash_to_curve, *};
|
||||
use crate::secret::Secret;
|
||||
let unblinded = unblind_message(&blinded_key, &r, &a).unwrap();
|
||||
|
||||
#[test]
|
||||
fn test_sign_message() {
|
||||
use super::*;
|
||||
let message = "test_message";
|
||||
let sec = SecretKey::new(ScalarPrimitive::ONE);
|
||||
|
||||
let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
|
||||
// A
|
||||
let bob_sec = SecretKey::new(ScalarPrimitive::ONE);
|
||||
|
||||
// C_
|
||||
let signed = sign_message(bob_sec, blinded_message.clone().into()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
signed,
|
||||
k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode(
|
||||
"025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// A
|
||||
let bob_sec = crate::nuts::SecretKey::from_hex(
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
|
||||
assert_eq!(
|
||||
PublicKey::from_hex(
|
||||
"03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap(),
|
||||
unblinded
|
||||
);
|
||||
}
|
||||
|
||||
// C_
|
||||
let signed = sign_message(bob_sec.into(), blinded_message.into()).unwrap();
|
||||
#[test]
|
||||
fn test_sign_message() {
|
||||
use super::*;
|
||||
let message = "test_message";
|
||||
let sec =
|
||||
SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
signed,
|
||||
k256::PublicKey::from_sec1_bytes(
|
||||
&hex::decode(
|
||||
"027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
|
||||
// A
|
||||
let bob_sec =
|
||||
SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
.unwrap();
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_blinded_dhke() {
|
||||
// a
|
||||
let bob_sec = SecretKey::random(&mut rand::thread_rng());
|
||||
// C_
|
||||
let signed = sign_message(&bob_sec, &blinded_message).unwrap();
|
||||
|
||||
// A
|
||||
let bob_pub = bob_sec.public_key();
|
||||
assert_eq!(
|
||||
signed,
|
||||
PublicKey::from_hex(
|
||||
"025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b"
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// let alice_sec = SecretKey::random(&mut rand::thread_rng());
|
||||
// A
|
||||
let bob_sec =
|
||||
SecretKey::from_hex("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
|
||||
.unwrap();
|
||||
|
||||
let x = Secret::new();
|
||||
// C_
|
||||
let signed = sign_message(&bob_sec, &blinded_message).unwrap();
|
||||
|
||||
// Y
|
||||
let y = hash_to_curve(&x.to_bytes()).unwrap();
|
||||
assert_eq!(
|
||||
signed,
|
||||
PublicKey::from_hex(
|
||||
"027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d"
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// B_
|
||||
let blinded = blind_message(&y.to_sec1_bytes(), None).unwrap();
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_blinded_dhke() {
|
||||
// a
|
||||
let bob_sec = SecretKey::generate();
|
||||
|
||||
// C_
|
||||
let signed = sign_message(bob_sec.clone(), blinded.0.into()).unwrap();
|
||||
// A
|
||||
let bob_pub = bob_sec.public_key();
|
||||
|
||||
// C
|
||||
let c = unblind_message(signed.into(), blinded.1, bob_pub.into()).unwrap();
|
||||
// let alice_sec = SecretKey::random(&mut rand::thread_rng());
|
||||
|
||||
assert!(verify_message(bob_sec, c.into(), &x.to_bytes()).is_ok());
|
||||
}
|
||||
let x = Secret::generate();
|
||||
|
||||
// Y
|
||||
let y = hash_to_curve(&x.to_bytes()).unwrap();
|
||||
|
||||
// B_
|
||||
let blinded = blind_message(&y.to_bytes(), None).unwrap();
|
||||
|
||||
// C_
|
||||
let signed = sign_message(&bob_sec, &blinded.0).unwrap();
|
||||
|
||||
// C
|
||||
let c = unblind_message(&signed, &blinded.1, &bob_pub).unwrap();
|
||||
|
||||
assert!(verify_message(&bob_sec, c, &x.to_bytes()).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use std::string::FromUtf8Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::util::hex;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Parse Url Error
|
||||
@@ -19,11 +21,10 @@ pub enum Error {
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
/// From hex error
|
||||
#[error("`{0}`")]
|
||||
HexError(#[from] hex::FromHexError),
|
||||
HexError(#[from] hex::Error),
|
||||
/// Secp256k1 error
|
||||
#[error("`{0}`")]
|
||||
EllipticCurve(#[from] k256::elliptic_curve::Error),
|
||||
#[error("`{0}`")]
|
||||
ECDSA(#[from] k256::ecdsa::Error),
|
||||
Secp256k1(#[from] bitcoin::secp256k1::Error),
|
||||
#[error("No Key for Amoun")]
|
||||
AmountKey,
|
||||
#[error("Amount miss match")]
|
||||
@@ -55,10 +56,12 @@ pub enum Error {
|
||||
#[error("`{0}`")]
|
||||
Secret(#[from] super::secret::Error),
|
||||
#[error("`{0}`")]
|
||||
NUT01(#[from] crate::nuts::nut01::Error),
|
||||
#[error("`{0}`")]
|
||||
NUT02(#[from] crate::nuts::nut02::Error),
|
||||
#[cfg(feature = "nut13")]
|
||||
#[error("`{0}`")]
|
||||
Bip32(#[from] bip32::Error),
|
||||
Bip32(#[from] bitcoin::bip32::Error),
|
||||
#[error("`{0}`")]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
/// Custom error
|
||||
@@ -87,20 +90,24 @@ impl ErrorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet {
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::nuts::nut01;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Serde Json error
|
||||
#[error("`{0}`")]
|
||||
SerdeJsonError(#[from] serde_json::Error),
|
||||
/// From elliptic curve
|
||||
/// Secp256k1 error
|
||||
#[error("`{0}`")]
|
||||
EllipticError(#[from] k256::elliptic_curve::Error),
|
||||
Secp256k1(#[from] bitcoin::secp256k1::Error),
|
||||
/// NUT01 error
|
||||
#[error("`{0}`")]
|
||||
NUT01(#[from] nut01::Error),
|
||||
/// Insufficient Funds
|
||||
#[error("Insufficient funds")]
|
||||
InsufficientFunds,
|
||||
@@ -135,10 +142,11 @@ pub mod wallet {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
pub mod mint {
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::nuts::nut01;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("No key for amount")]
|
||||
@@ -147,9 +155,12 @@ pub mod mint {
|
||||
Amount,
|
||||
#[error("Token Already Spent")]
|
||||
TokenSpent,
|
||||
/// From elliptic curve
|
||||
/// Secp256k1 error
|
||||
#[error("`{0}`")]
|
||||
EllipticError(#[from] k256::elliptic_curve::Error),
|
||||
Secp256k1(#[from] bitcoin::secp256k1::Error),
|
||||
/// NUT01 error
|
||||
#[error("`{0}`")]
|
||||
NUT01(#[from] nut01::Error),
|
||||
#[error("`Token not verified`")]
|
||||
TokenNotVerifed,
|
||||
#[error("Invoice amount undefined")]
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
extern crate core;
|
||||
|
||||
pub use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
pub use bitcoin::secp256k1;
|
||||
pub use lightning_invoice::{self, Bolt11Invoice};
|
||||
|
||||
pub mod amount;
|
||||
#[cfg(any(feature = "wallet", feature = "mint"))]
|
||||
pub mod dhke;
|
||||
@@ -7,10 +13,9 @@ pub mod secret;
|
||||
pub mod serde_utils;
|
||||
pub mod types;
|
||||
pub mod url;
|
||||
pub mod utils;
|
||||
pub mod util;
|
||||
|
||||
pub use self::amount::Amount;
|
||||
pub use self::util::SECP256K1;
|
||||
|
||||
pub use amount::Amount;
|
||||
pub use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
pub use lightning_invoice::Bolt11Invoice;
|
||||
pub use {k256, lightning_invoice};
|
||||
pub type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
||||
@@ -168,7 +168,7 @@ pub mod wallet {
|
||||
let mut output = Vec::with_capacity(amount_split.len());
|
||||
|
||||
for amount in amount_split {
|
||||
let secret = Secret::new();
|
||||
let secret = Secret::generate();
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
|
||||
|
||||
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
|
||||
@@ -176,7 +176,7 @@ pub mod wallet {
|
||||
output.push(PreMint {
|
||||
secret,
|
||||
blinded_message,
|
||||
r: r.into(),
|
||||
r,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
@@ -199,7 +199,7 @@ pub mod wallet {
|
||||
output.push(PreMint {
|
||||
secret,
|
||||
blinded_message,
|
||||
r: r.into(),
|
||||
r,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
@@ -214,7 +214,7 @@ pub mod wallet {
|
||||
let mut output = Vec::with_capacity(count as usize);
|
||||
|
||||
for _i in 0..count {
|
||||
let secret = Secret::new();
|
||||
let secret = Secret::generate();
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
|
||||
|
||||
let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
|
||||
@@ -222,7 +222,7 @@ pub mod wallet {
|
||||
output.push(PreMint {
|
||||
secret,
|
||||
blinded_message,
|
||||
r: r.into(),
|
||||
r,
|
||||
amount: Amount::ZERO,
|
||||
})
|
||||
}
|
||||
@@ -249,7 +249,7 @@ pub mod wallet {
|
||||
output.push(PreMint {
|
||||
secret,
|
||||
blinded_message,
|
||||
r: r.into(),
|
||||
r,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
@@ -490,6 +490,7 @@ impl PartialOrd for Proof {
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
use super::wallet::*;
|
||||
use super::*;
|
||||
|
||||
@@ -507,6 +508,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wallet")]
|
||||
fn test_token_str_round_trip() {
|
||||
let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
|
||||
|
||||
@@ -530,6 +532,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wallet")]
|
||||
fn test_blank_blinded_messages() {
|
||||
// TODO: Need to update id to new type in proof
|
||||
let b = PreMintSecrets::blank(
|
||||
@@ -546,6 +549,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wallet")]
|
||||
fn incorrect_tokens() {
|
||||
let incorrect_prefix = "casshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
146
crates/cashu/src/nuts/nut01/public_key.rs
Normal file
146
crates/cashu/src/nuts/nut01/public_key.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use core::fmt;
|
||||
use core::ops::Deref;
|
||||
use core::str::FromStr;
|
||||
|
||||
use bitcoin::secp256k1;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PublicKey {
|
||||
inner: secp256k1::PublicKey,
|
||||
}
|
||||
|
||||
impl Deref for PublicKey {
|
||||
type Target = secp256k1::PublicKey;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<secp256k1::PublicKey> for PublicKey {
|
||||
fn from(inner: secp256k1::PublicKey) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
/// Parse from `bytes`
|
||||
#[inline]
|
||||
pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
inner: secp256k1::PublicKey::from_slice(slice)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse from `hex` string
|
||||
#[inline]
|
||||
pub fn from_hex<S>(hex: S) -> Result<Self, Error>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let hex: &str = hex.as_ref();
|
||||
|
||||
// Check size
|
||||
if hex.len() != 33 * 2 {
|
||||
return Err(Error::InvalidPublicKeySize {
|
||||
expected: 33,
|
||||
found: hex.len() / 2,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
inner: secp256k1::PublicKey::from_str(hex)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_bytes(&self) -> [u8; 33] {
|
||||
self.inner.serialize()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_uncompressed_bytes(&self) -> [u8; 65] {
|
||||
self.inner.serialize_uncompressed()
|
||||
}
|
||||
|
||||
/// Get public key as `hex` string
|
||||
#[inline]
|
||||
pub fn to_hex(&self) -> String {
|
||||
self.inner.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicKey {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(hex: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_hex(hex)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PublicKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PublicKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PublicKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let public_key: String = String::deserialize(deserializer)?;
|
||||
Self::from_hex(public_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn test_public_key_from_hex() {
|
||||
// Compressed
|
||||
assert!(
|
||||
(PublicKey::from_hex(
|
||||
"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104"
|
||||
)
|
||||
.is_ok())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_invalid_public_key_from_hex() {
|
||||
// Uncompressed (is valid but is cashu must be compressed?)
|
||||
assert!((PublicKey::from_hex("04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481")
|
||||
.is_err()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(bench)]
|
||||
mod benches {
|
||||
use test::{black_box, Bencher};
|
||||
|
||||
use super::*;
|
||||
|
||||
const HEX: &str = "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104";
|
||||
|
||||
#[bench]
|
||||
pub fn public_key_from_hex(bh: &mut Bencher) {
|
||||
bh.iter(|| {
|
||||
black_box(PublicKey::from_hex(HEX)).unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
126
crates/cashu/src/nuts/nut01/secret_key.rs
Normal file
126
crates/cashu/src/nuts/nut01/secret_key.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use core::fmt;
|
||||
use core::ops::Deref;
|
||||
use core::str::FromStr;
|
||||
|
||||
use bitcoin::secp256k1;
|
||||
use bitcoin::secp256k1::rand::rngs::OsRng;
|
||||
use bitcoin::secp256k1::Scalar;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::{Error, PublicKey};
|
||||
use crate::SECP256K1;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SecretKey {
|
||||
inner: secp256k1::SecretKey,
|
||||
}
|
||||
|
||||
impl Deref for SecretKey {
|
||||
type Target = secp256k1::SecretKey;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<secp256k1::SecretKey> for SecretKey {
|
||||
fn from(inner: secp256k1::SecretKey) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SecretKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_secret_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
/// Parse from `bytes`
|
||||
pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
inner: secp256k1::SecretKey::from_slice(slice)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse from `hex` string
|
||||
pub fn from_hex<S>(hex: S) -> Result<Self, Error>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Ok(Self {
|
||||
inner: secp256k1::SecretKey::from_str(hex.as_ref())?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate random secret key
|
||||
pub fn generate() -> Self {
|
||||
let (secret_key, _) = SECP256K1.generate_keypair(&mut OsRng);
|
||||
Self { inner: secret_key }
|
||||
}
|
||||
|
||||
/// Get secret key as `hex` string
|
||||
pub fn to_secret_hex(&self) -> String {
|
||||
self.inner.display_secret().to_string()
|
||||
}
|
||||
|
||||
/// Get secret key as `bytes`
|
||||
pub fn as_secret_bytes(&self) -> &[u8] {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
|
||||
/// Get secret key as `bytes`
|
||||
pub fn to_secret_bytes(&self) -> [u8; 32] {
|
||||
self.inner.secret_bytes()
|
||||
}
|
||||
|
||||
/// Get public key
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.inner.public_key(&SECP256K1).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_scalar(self) -> Scalar {
|
||||
Scalar::from(self.inner)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_scalar(&self) -> Scalar {
|
||||
Scalar::from(self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SecretKey {
|
||||
type Err = Error;
|
||||
|
||||
/// Try to parse [SecretKey] from `hex` or `bech32`
|
||||
fn from_str(secret_key: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_hex(secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SecretKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_secret_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SecretKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let secret_key: String = String::deserialize(deserializer)?;
|
||||
Self::from_hex(secret_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SecretKey {
|
||||
fn drop(&mut self) {
|
||||
self.inner.non_secure_erase();
|
||||
tracing::trace!("Secret Key dropped.");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
//! Keysets and keyset ID
|
||||
// https://github.com/cashubtc/nuts/blob/main/02.md
|
||||
//! NUT-02: Keysets and keyset ID
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/02.md>
|
||||
|
||||
use core::fmt;
|
||||
use std::str::FromStr;
|
||||
use core::str::FromStr;
|
||||
|
||||
use bitcoin::hashes::{sha256, Hash};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, VecSkipError};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::nut01::Keys;
|
||||
use super::CurrencyUnit;
|
||||
use crate::util::hex;
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum Error {
|
||||
#[error("`{0}`")]
|
||||
HexError(#[from] hex::FromHexError),
|
||||
#[error(transparent)]
|
||||
HexError(#[from] hex::Error),
|
||||
#[error("NUT02: ID length invalid")]
|
||||
Length,
|
||||
}
|
||||
@@ -25,8 +26,9 @@ pub enum Error {
|
||||
pub enum KeySetVersion {
|
||||
Version00,
|
||||
}
|
||||
impl std::fmt::Display for KeySetVersion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
impl fmt::Display for KeySetVersion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
KeySetVersion::Version00 => f.write_str("00"),
|
||||
}
|
||||
@@ -60,8 +62,8 @@ impl TryFrom<Id> for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Id {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for Id {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&format!(
|
||||
"{}{}",
|
||||
self.version,
|
||||
@@ -105,7 +107,7 @@ impl<'de> serde::de::Deserialize<'de> for Id {
|
||||
impl<'de> serde::de::Visitor<'de> for IdVisitor {
|
||||
type Value = Id;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Expecting a 14 char hex string")
|
||||
}
|
||||
|
||||
@@ -141,11 +143,12 @@ impl From<&Keys> for Id {
|
||||
5 - prefix it with a keyset ID version byte
|
||||
*/
|
||||
|
||||
let pubkeys_concat = map
|
||||
// Note: Keys are a BTreeMap so are already sorted by amount in ascending order
|
||||
|
||||
let pubkeys_concat: Vec<u8> = map
|
||||
.iter()
|
||||
.sorted_by(|(amt_a, _), (amt_b, _)| amt_a.cmp(amt_b))
|
||||
.map(|(_, pubkey)| pubkey.to_bytes())
|
||||
.collect::<Box<[Box<[u8]>]>>()
|
||||
.collect::<Vec<[u8; 33]>>()
|
||||
.concat();
|
||||
|
||||
let hash = sha256::Hash::hash(&pubkeys_concat);
|
||||
@@ -219,11 +222,11 @@ pub mod mint {
|
||||
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::hashes::{Hash, HashEngine};
|
||||
use k256::SecretKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Id;
|
||||
use crate::nuts::nut01::mint::{KeyPair, Keys};
|
||||
use crate::nuts::nut01::SecretKey;
|
||||
use crate::nuts::CurrencyUnit;
|
||||
use crate::Amount;
|
||||
|
||||
@@ -262,8 +265,8 @@ pub mod mint {
|
||||
let mut e = engine.clone();
|
||||
e.input(i.to_string().as_bytes());
|
||||
let hash = Sha256::from_engine(e);
|
||||
let secret_key = SecretKey::from_slice(&hash.to_byte_array()).unwrap();
|
||||
let keypair = KeyPair::from_secret_key(secret_key.into());
|
||||
let secret_key = SecretKey::from_slice(&hash.to_byte_array()).unwrap(); // TODO: remove unwrap
|
||||
let keypair = KeyPair::from_secret_key(secret_key);
|
||||
map.insert(amount, keypair);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Swap
|
||||
// https://github.com/cashubtc/nuts/blob/main/03.md
|
||||
//! NUT-03: Swap
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/03.md>
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Mint Tokens via Bolt11
|
||||
// https://github.com/cashubtc/nuts/blob/main/04.md
|
||||
//! NUT-04: Mint Tokens via Bolt11
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/04.md>
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Melting Tokens
|
||||
// https://github.com/cashubtc/nuts/blob/main/05.md
|
||||
//! NUT-05: Melting Tokens
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/05.md>
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Mint Information
|
||||
// https://github.com/cashubtc/nuts/blob/main/09.md
|
||||
//! NUT-06: Mint Information
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/06.md>
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Spendable Check
|
||||
// https://github.com/cashubtc/nuts/blob/main/07.md
|
||||
//! NUT-07: Spendable Check
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/07.md>
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Lightning fee return
|
||||
// https://github.com/cashubtc/nuts/blob/main/08.md
|
||||
//! NUT-08: Lightning fee return
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/08.md>
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//! Nut-09: Restore signatures
|
||||
//! NUT-09: Restore signatures
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/09.md>
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use std::str::FromStr;
|
||||
//! NUT-10: Spending conditions
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/10.md>
|
||||
|
||||
use core::str::FromStr;
|
||||
|
||||
use serde::ser::SerializeTuple;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
@@ -35,7 +39,7 @@ impl Secret {
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
let nonce = crate::secret::Secret::new().to_string();
|
||||
let nonce = crate::secret::Secret::generate().to_string();
|
||||
let secret_data = SecretData {
|
||||
nonce,
|
||||
data: data.into(),
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//! Pay to Public Key (P2PK)
|
||||
// https://github.com/cashubtc/nuts/blob/main/11.md
|
||||
//! NUT-11: Pay to Public Key (P2PK)
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/11.md>
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use k256::schnorr::signature::{Signer, Verifier};
|
||||
use k256::schnorr::Signature;
|
||||
use log::debug;
|
||||
use bitcoin::hashes::sha256::Hash as Sha256Hash;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::secp256k1::schnorr::Signature;
|
||||
use bitcoin::secp256k1::{
|
||||
KeyPair, Message, Parity, PublicKey as NormalizedPublicKey, XOnlyPublicKey,
|
||||
};
|
||||
use serde::de::Error as DeserializerError;
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
|
||||
@@ -17,7 +22,8 @@ use super::nut10::{Secret, SecretData};
|
||||
use super::{Proof, SecretKey};
|
||||
use crate::error::Error;
|
||||
use crate::nuts::nut00::BlindedMessage;
|
||||
use crate::utils::unix_time;
|
||||
use crate::util::{hex, unix_time};
|
||||
use crate::SECP256K1;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Signatures {
|
||||
@@ -54,26 +60,25 @@ impl Proof {
|
||||
}
|
||||
|
||||
let secret: Secret = self.secret.clone().try_into()?;
|
||||
|
||||
let spending_conditions: P2PKConditions = secret.clone().try_into()?;
|
||||
let msg: &[u8] = self.secret.as_bytes();
|
||||
|
||||
let mut valid_sigs = 0;
|
||||
|
||||
let msg = &self.secret.to_bytes();
|
||||
if let Some(witness) = &self.witness {
|
||||
for signature in &witness.signatures {
|
||||
for signature in witness.signatures.iter() {
|
||||
let mut pubkeys = spending_conditions.pubkeys.clone();
|
||||
let data_key = VerifyingKey::from_str(&secret.secret_data.data)?;
|
||||
pubkeys.push(data_key);
|
||||
|
||||
pubkeys.push(VerifyingKey::from_str(&secret.secret_data.data)?);
|
||||
|
||||
for v in &spending_conditions.pubkeys {
|
||||
let sig = Signature::try_from(hex::decode(signature)?.as_slice())?;
|
||||
let sig = Signature::from_str(signature)?;
|
||||
|
||||
if v.verify(msg, &sig).is_ok() {
|
||||
valid_sigs += 1;
|
||||
} else {
|
||||
debug!(
|
||||
"Could not verify signature: {} on message: {}",
|
||||
hex::encode(sig.to_bytes()),
|
||||
tracing::debug!(
|
||||
"Could not verify signature: {sig} on message: {}",
|
||||
self.secret.to_string()
|
||||
)
|
||||
}
|
||||
@@ -81,7 +86,7 @@ impl Proof {
|
||||
}
|
||||
}
|
||||
|
||||
if valid_sigs.ge(&spending_conditions.num_sigs.unwrap_or(1)) {
|
||||
if valid_sigs >= spending_conditions.num_sigs.unwrap_or(1) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -94,8 +99,8 @@ impl Proof {
|
||||
if let Some(signatures) = &self.witness {
|
||||
for s in &signatures.signatures {
|
||||
for v in &refund_keys {
|
||||
let sig = Signature::try_from(hex::decode(s)?.as_slice())
|
||||
.map_err(|_| Error::InvalidSignature)?;
|
||||
let sig =
|
||||
Signature::from_str(s).map_err(|_| Error::InvalidSignature)?;
|
||||
|
||||
// As long as there is one valid refund signature it can be spent
|
||||
if v.verify(msg, &sig).is_ok() {
|
||||
@@ -111,15 +116,14 @@ impl Proof {
|
||||
}
|
||||
|
||||
pub fn sign_p2pk(&mut self, secret_key: SigningKey) -> Result<(), Error> {
|
||||
let msg_to_sign = &self.secret.to_bytes();
|
||||
|
||||
let signature = secret_key.sign(msg_to_sign);
|
||||
let msg: Vec<u8> = self.secret.to_bytes();
|
||||
let signature: Signature = secret_key.sign(&msg)?;
|
||||
|
||||
self.witness
|
||||
.as_mut()
|
||||
.unwrap_or(&mut Signatures::default())
|
||||
.signatures
|
||||
.push(hex::encode(signature.to_bytes()));
|
||||
.push(signature.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -127,15 +131,14 @@ impl Proof {
|
||||
|
||||
impl BlindedMessage {
|
||||
pub fn sign_p2pk(&mut self, secret_key: SigningKey) -> Result<(), Error> {
|
||||
let msg_to_sign = hex::decode(self.b.to_string())?;
|
||||
|
||||
let signature = secret_key.sign(&msg_to_sign);
|
||||
let msg: [u8; 33] = self.b.to_bytes();
|
||||
let signature: Signature = secret_key.sign(&msg)?;
|
||||
|
||||
self.witness
|
||||
.as_mut()
|
||||
.unwrap_or(&mut Signatures::default())
|
||||
.signatures
|
||||
.push(hex::encode(signature.to_bytes()));
|
||||
.push(signature.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -150,16 +153,12 @@ impl BlindedMessage {
|
||||
for signature in &witness.signatures {
|
||||
for v in pubkeys {
|
||||
let msg = &self.b.to_bytes();
|
||||
let sig = Signature::try_from(hex::decode(signature)?.as_slice())?;
|
||||
let sig = Signature::from_str(signature)?;
|
||||
|
||||
if v.verify(msg, &sig).is_ok() {
|
||||
valid_sigs += 1;
|
||||
} else {
|
||||
debug!(
|
||||
"Could not verify signature: {} on message: {}",
|
||||
hex::encode(sig.to_bytes()),
|
||||
self.b.to_string()
|
||||
)
|
||||
tracing::debug!("Could not verify signature: {sig} on message: {}", self.b)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,11 +224,11 @@ impl TryFrom<P2PKConditions> for Secret {
|
||||
return Err(Error::Amount);
|
||||
}
|
||||
|
||||
let data: PublicKey = pubkeys[0].clone().into();
|
||||
let data: PublicKey = pubkeys[0].clone().to_normalized_public_key();
|
||||
|
||||
let data = data.to_string();
|
||||
|
||||
let mut tags = vec![];
|
||||
let mut tags = Vec::new();
|
||||
|
||||
if pubkeys.len().gt(&1) {
|
||||
tags.push(Tag::PubKeys(pubkeys.into_iter().skip(1).collect()).as_vec());
|
||||
@@ -488,8 +487,8 @@ impl From<Tag> for Vec<String> {
|
||||
Tag::PubKeys(pubkeys) => {
|
||||
let mut tag = vec![TagKind::Pubkeys.to_string()];
|
||||
|
||||
for pubkey in pubkeys {
|
||||
let pubkey: PublicKey = pubkey.into();
|
||||
for pubkey in pubkeys.into_iter() {
|
||||
let pubkey: PublicKey = pubkey.to_normalized_public_key();
|
||||
tag.push(pubkey.to_string())
|
||||
}
|
||||
tag
|
||||
@@ -533,19 +532,26 @@ impl<'de> Deserialize<'de> for Tag {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct VerifyingKey(k256::schnorr::VerifyingKey);
|
||||
pub struct VerifyingKey(XOnlyPublicKey);
|
||||
|
||||
impl VerifyingKey {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
Ok(VerifyingKey(k256::schnorr::VerifyingKey::from_bytes(
|
||||
bytes,
|
||||
)?))
|
||||
Ok(Self(XOnlyPublicKey::from_slice(bytes)?))
|
||||
}
|
||||
|
||||
pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
|
||||
self.0
|
||||
.verify(msg, signature)
|
||||
.map_err(|_| Error::InvalidSignature)?;
|
||||
pub fn from_public_key(public_key: PublicKey) -> Self {
|
||||
let (pk, ..) = public_key.x_only_public_key();
|
||||
Self(pk)
|
||||
}
|
||||
|
||||
pub fn to_normalized_public_key(self) -> PublicKey {
|
||||
NormalizedPublicKey::from_x_only_public_key(self.0, Parity::Even).into()
|
||||
}
|
||||
|
||||
pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<(), Error> {
|
||||
let hash: Sha256Hash = Sha256Hash::hash(msg);
|
||||
let msg = Message::from_slice(hash.as_ref())?;
|
||||
SECP256K1.verify_schnorr(sig, &msg, &self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -554,42 +560,22 @@ impl FromStr for VerifyingKey {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(hex: &str) -> Result<Self, Self::Err> {
|
||||
let bytes = hex::decode(hex)?;
|
||||
let bytes: Vec<u8> = hex::decode(hex)?;
|
||||
|
||||
let bytes = if bytes.len().eq(&33) {
|
||||
bytes.iter().skip(1).cloned().collect()
|
||||
// Check len
|
||||
let bytes: &[u8] = if bytes.len() == 33 {
|
||||
&bytes[1..]
|
||||
} else {
|
||||
bytes.to_vec()
|
||||
&bytes
|
||||
};
|
||||
|
||||
Ok(VerifyingKey(
|
||||
k256::schnorr::VerifyingKey::from_bytes(&bytes).map_err(|_| Error::Key)?,
|
||||
))
|
||||
Ok(Self(XOnlyPublicKey::from_slice(bytes)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VerifyingKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let bytes = self.0.to_bytes();
|
||||
f.write_str(&hex::encode(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VerifyingKey> for k256::schnorr::VerifyingKey {
|
||||
fn from(value: VerifyingKey) -> k256::schnorr::VerifyingKey {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VerifyingKey> for k256::schnorr::VerifyingKey {
|
||||
fn from(value: &VerifyingKey) -> k256::schnorr::VerifyingKey {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<k256::schnorr::VerifyingKey> for VerifyingKey {
|
||||
fn from(value: k256::schnorr::VerifyingKey) -> VerifyingKey {
|
||||
VerifyingKey(value)
|
||||
impl fmt::Display for VerifyingKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,38 +602,37 @@ impl TryFrom<&PublicKey> for VerifyingKey {
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct SigningKey(k256::schnorr::SigningKey);
|
||||
|
||||
impl From<SigningKey> for k256::schnorr::SigningKey {
|
||||
fn from(value: SigningKey) -> k256::schnorr::SigningKey {
|
||||
value.0
|
||||
}
|
||||
pub struct SigningKey {
|
||||
secret_key: SecretKey,
|
||||
key_pair: KeyPair,
|
||||
}
|
||||
|
||||
impl From<k256::schnorr::SigningKey> for SigningKey {
|
||||
fn from(value: k256::schnorr::SigningKey) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl Deref for SigningKey {
|
||||
type Target = SecretKey;
|
||||
|
||||
impl From<SecretKey> for SigningKey {
|
||||
fn from(value: SecretKey) -> SigningKey {
|
||||
value.into()
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.secret_key
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningKey {
|
||||
pub fn public_key(&self) -> VerifyingKey {
|
||||
(*self.0.verifying_key()).into()
|
||||
}
|
||||
|
||||
pub fn sign(&self, msg: &[u8]) -> Signature {
|
||||
self.0.sign(msg)
|
||||
#[inline]
|
||||
pub fn new(secret_key: SecretKey) -> Self {
|
||||
Self {
|
||||
key_pair: KeyPair::from_secret_key(&SECP256K1, &secret_key),
|
||||
secret_key,
|
||||
}
|
||||
}
|
||||
pub fn sign(&self, msg: &[u8]) -> Result<Signature, Error> {
|
||||
let hash: Sha256Hash = Sha256Hash::hash(msg);
|
||||
let msg = Message::from_slice(hash.as_ref())?;
|
||||
Ok(SECP256K1.sign_schnorr(&msg, &self.key_pair))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn verifying_key(&self) -> VerifyingKey {
|
||||
VerifyingKey(*self.0.verifying_key())
|
||||
let public_key: PublicKey = self.public_key();
|
||||
VerifyingKey::from_public_key(public_key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,30 +640,19 @@ impl FromStr for SigningKey {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(hex: &str) -> Result<Self, Self::Err> {
|
||||
let bytes = hex::decode(hex)?;
|
||||
|
||||
let bytes = if bytes.len().eq(&33) {
|
||||
bytes.iter().skip(1).cloned().collect()
|
||||
} else {
|
||||
bytes.to_vec()
|
||||
};
|
||||
|
||||
Ok(SigningKey(
|
||||
k256::schnorr::SigningKey::from_bytes(&bytes).map_err(|_| Error::Key)?,
|
||||
))
|
||||
let secret_key = SecretKey::from_hex(hex)?;
|
||||
Ok(Self::new(secret_key))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SigningKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let bytes = self.0.to_bytes();
|
||||
|
||||
f.write_str(&hex::encode(bytes))
|
||||
impl fmt::Display for SigningKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
@@ -771,10 +745,16 @@ mod tests {
|
||||
#[test]
|
||||
fn test_verify() {
|
||||
// Proof with a valid signature
|
||||
let valid_proof = r#"{"amount":1,"secret":"[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id":"009a1f293253e41e","witness":"{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}"}"#;
|
||||
|
||||
let valid_proof: Proof = serde_json::from_str(valid_proof).unwrap();
|
||||
let json: &str = r#"{
|
||||
"amount":1,
|
||||
"secret":"[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
|
||||
"C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
|
||||
"id":"009a1f293253e41e",
|
||||
"witness":"{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}"
|
||||
}"#;
|
||||
let valid_proof: Proof = serde_json::from_str(json).unwrap();
|
||||
|
||||
valid_proof.verify_p2pk().unwrap();
|
||||
assert!(valid_proof.verify_p2pk().is_ok());
|
||||
|
||||
// Proof with a signature that is in a different secret
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
//! NUT-12: Offline ecash signature validation
|
||||
//! https://github.com/cashubtc/nuts/blob/main/12.md
|
||||
use std::ops::Mul;
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/12.md>
|
||||
|
||||
use k256::Scalar;
|
||||
use log::{debug, warn};
|
||||
use core::ops::Deref;
|
||||
|
||||
use bitcoin::secp256k1::{self, Scalar};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{BlindSignature, Id, Proof, PublicKey, SecretKey};
|
||||
use crate::dhke::{hash_e, hash_to_curve};
|
||||
use crate::Amount;
|
||||
use crate::{Amount, SECP256K1};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
@@ -20,9 +21,11 @@ pub enum Error {
|
||||
#[error("Invalid Dleq Prood")]
|
||||
InvalidDleqProof,
|
||||
#[error("`{0}`")]
|
||||
EllipticCurve(#[from] k256::elliptic_curve::Error),
|
||||
#[error("`{0}`")]
|
||||
Cashu(#[from] crate::error::Error),
|
||||
#[error("`{0}`")]
|
||||
NUT01(#[from] crate::nuts::nut01::Error),
|
||||
#[error("`{0}`")]
|
||||
Secp256k1(#[from] secp256k1::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -38,37 +41,41 @@ pub struct ProofDleq {
|
||||
pub r: SecretKey,
|
||||
}
|
||||
|
||||
/// Verify DLEQ
|
||||
fn verify_dleq(
|
||||
blinded_message: k256::PublicKey,
|
||||
blinded_signature: k256::PublicKey,
|
||||
e: k256::SecretKey,
|
||||
s: k256::SecretKey,
|
||||
mint_pubkey: k256::PublicKey,
|
||||
blinded_message: PublicKey, // B'
|
||||
blinded_signature: PublicKey, // C'
|
||||
e: &SecretKey,
|
||||
s: &SecretKey,
|
||||
mint_pubkey: PublicKey, // A
|
||||
) -> Result<(), Error> {
|
||||
let r1 = s.public_key().to_projective()
|
||||
- mint_pubkey
|
||||
.as_affine()
|
||||
.mul(Scalar::from(e.as_scalar_primitive()));
|
||||
let e_bytes: [u8; 32] = e.to_secret_bytes();
|
||||
let e: Scalar = e.as_scalar();
|
||||
|
||||
let r2 = blinded_message
|
||||
.as_affine()
|
||||
.mul(Scalar::from(s.as_scalar_primitive()))
|
||||
- blinded_signature
|
||||
.as_affine()
|
||||
.mul(Scalar::from(e.as_scalar_primitive()));
|
||||
// a = e*A
|
||||
let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &e)?.into();
|
||||
|
||||
let e_bytes = e.to_bytes().to_vec();
|
||||
// R1 = s*G - a
|
||||
let a: PublicKey = a.negate(&SECP256K1).into();
|
||||
let r1: PublicKey = s.public_key().combine(&a)?.into(); // s*G + (-a)
|
||||
|
||||
let hash_e = hash_e(vec![
|
||||
k256::PublicKey::try_from(r1)?,
|
||||
k256::PublicKey::try_from(r2)?,
|
||||
mint_pubkey,
|
||||
blinded_signature,
|
||||
]);
|
||||
// b = s*B'
|
||||
let s: Scalar = Scalar::from(s.deref().to_owned());
|
||||
let b: PublicKey = blinded_message.mul_tweak(&SECP256K1, &s)?.into();
|
||||
|
||||
if e_bytes.ne(&hash_e) {
|
||||
warn!("DLEQ on signature failed");
|
||||
debug!("e_bytes: {:?}, Hash e: {:?}", e_bytes, hash_e);
|
||||
// c = e*C'
|
||||
let c: PublicKey = blinded_signature.mul_tweak(&SECP256K1, &e)?.into();
|
||||
|
||||
// R2 = b - c
|
||||
let c: PublicKey = c.negate(&SECP256K1).into();
|
||||
let r2: PublicKey = b.combine(&c)?.into();
|
||||
|
||||
// hash(R1,R2,A,C')
|
||||
let hash_e: [u8; 32] = hash_e([r1, r2, mint_pubkey, blinded_signature]);
|
||||
|
||||
if e_bytes != hash_e {
|
||||
tracing::warn!("DLEQ on signature failed");
|
||||
tracing::debug!("e_bytes: {:?}, hash_e: {:?}", e_bytes, hash_e);
|
||||
return Err(Error::InvalidDleqProof);
|
||||
}
|
||||
|
||||
@@ -76,103 +83,90 @@ fn verify_dleq(
|
||||
}
|
||||
|
||||
fn calculate_dleq(
|
||||
blinded_signature: k256::PublicKey,
|
||||
blinded_message: &k256::PublicKey,
|
||||
mint_secretkey: &k256::SecretKey,
|
||||
blinded_signature: PublicKey, // C'
|
||||
blinded_message: &PublicKey, // B'
|
||||
mint_secret_key: &SecretKey, // a
|
||||
) -> Result<BlindSignatureDleq, Error> {
|
||||
// Random nonce
|
||||
let r: k256::SecretKey = SecretKey::random().into();
|
||||
let r: SecretKey = SecretKey::generate();
|
||||
|
||||
// R1 = r*G
|
||||
let r1 = r.public_key();
|
||||
|
||||
let r2: k256::PublicKey = blinded_message
|
||||
.as_affine()
|
||||
.mul(Scalar::from(r.as_scalar_primitive()))
|
||||
.try_into()?;
|
||||
// R2 = r*B'
|
||||
let r_scal: Scalar = r.as_scalar();
|
||||
let r2: PublicKey = blinded_message.mul_tweak(&SECP256K1, &r_scal)?.into();
|
||||
|
||||
let e = hash_e(vec![r1, r2, mint_secretkey.public_key(), blinded_signature]);
|
||||
// e = hash(R1,R2,A,C')
|
||||
let e: [u8; 32] = hash_e([r1, r2, mint_secret_key.public_key(), blinded_signature]);
|
||||
let e_sk: SecretKey = SecretKey::from_slice(&e)?;
|
||||
|
||||
let e_sk = k256::SecretKey::from_slice(&e)?;
|
||||
// s1 = e*a
|
||||
let s1: SecretKey = e_sk.mul_tweak(&mint_secret_key.as_scalar())?.into();
|
||||
|
||||
let s = Scalar::from(r.as_scalar_primitive())
|
||||
+ Scalar::from(e_sk.as_scalar_primitive())
|
||||
* Scalar::from(mint_secretkey.as_scalar_primitive());
|
||||
// s = r + s1
|
||||
let s: SecretKey = r.add_tweak(&s1.to_scalar())?.into();
|
||||
|
||||
let s: k256::SecretKey = k256::SecretKey::new(s.into());
|
||||
|
||||
Ok(BlindSignatureDleq {
|
||||
e: e_sk.into(),
|
||||
s: s.into(),
|
||||
})
|
||||
Ok(BlindSignatureDleq { e: e_sk, s })
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
pub fn verify_dleq(&self, mint_pubkey: &PublicKey) -> Result<(), Error> {
|
||||
let (e, s, blinding_factor): (k256::SecretKey, k256::SecretKey, k256::SecretKey) =
|
||||
if let Some(dleq) = self.dleq.clone() {
|
||||
(dleq.e.into(), dleq.s.into(), dleq.r.into())
|
||||
} else {
|
||||
return Err(Error::MissingDleqProof);
|
||||
};
|
||||
pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> {
|
||||
match &self.dleq {
|
||||
Some(dleq) => {
|
||||
let y = hash_to_curve(self.secret.as_bytes())?;
|
||||
|
||||
let c: k256::PublicKey = (&self.c).into();
|
||||
let mint_pubkey: k256::PublicKey = mint_pubkey.into();
|
||||
let r: Scalar = dleq.r.as_scalar();
|
||||
let bs1: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
|
||||
|
||||
let y = hash_to_curve(self.secret.0.as_bytes())?;
|
||||
let blinded_signature = c.to_projective()
|
||||
+ mint_pubkey
|
||||
.as_affine()
|
||||
.mul(Scalar::from(blinding_factor.as_scalar_primitive()));
|
||||
let blinded_message = y.to_projective() + blinding_factor.public_key().to_projective();
|
||||
let blinded_signature: PublicKey = self.c.combine(&bs1)?.into();
|
||||
let blinded_message: PublicKey = y.combine(&dleq.r.public_key())?.into();
|
||||
|
||||
let blinded_signature = k256::PublicKey::try_from(blinded_signature)?;
|
||||
let blinded_message = k256::PublicKey::try_from(blinded_message)?;
|
||||
|
||||
verify_dleq(blinded_message, blinded_signature, e, s, mint_pubkey)
|
||||
verify_dleq(
|
||||
blinded_message,
|
||||
blinded_signature,
|
||||
&dleq.e,
|
||||
&dleq.s,
|
||||
mint_pubkey,
|
||||
)
|
||||
}
|
||||
None => Err(Error::MissingDleqProof),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlindSignature {
|
||||
pub fn new_dleq(
|
||||
/// New DLEQ
|
||||
#[inline]
|
||||
pub fn new(
|
||||
amount: Amount,
|
||||
blinded_signature: PublicKey,
|
||||
keyset_id: Id,
|
||||
blinded_message: &PublicKey,
|
||||
mint_secretkey: SecretKey,
|
||||
) -> Result<Self, Error> {
|
||||
let blinded_message: k256::PublicKey = blinded_message.into();
|
||||
let mint_secretkey: k256::SecretKey = mint_secretkey.into();
|
||||
|
||||
let dleq = calculate_dleq(
|
||||
blinded_signature.clone().into(),
|
||||
&blinded_message,
|
||||
&mint_secretkey,
|
||||
)?;
|
||||
|
||||
Ok(BlindSignature {
|
||||
Ok(Self {
|
||||
amount,
|
||||
keyset_id,
|
||||
c: blinded_signature,
|
||||
dleq: Some(dleq),
|
||||
dleq: Some(calculate_dleq(
|
||||
blinded_signature,
|
||||
blinded_message,
|
||||
&mint_secretkey,
|
||||
)?),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn verify_dleq(
|
||||
&self,
|
||||
mint_pubkey: &PublicKey,
|
||||
blinded_message: &PublicKey,
|
||||
mint_pubkey: PublicKey,
|
||||
blinded_message: PublicKey,
|
||||
) -> Result<(), Error> {
|
||||
let (e, s): (k256::SecretKey, k256::SecretKey) = if let Some(dleq) = &self.dleq {
|
||||
(dleq.e.clone().into(), dleq.s.clone().into())
|
||||
} else {
|
||||
return Err(Error::MissingDleqProof);
|
||||
};
|
||||
|
||||
let mint_pubkey: k256::PublicKey = mint_pubkey.into();
|
||||
let blinded_message: k256::PublicKey = blinded_message.into();
|
||||
|
||||
let c: k256::PublicKey = (&self.c).into();
|
||||
verify_dleq(blinded_message, c, e, s, mint_pubkey)
|
||||
match &self.dleq {
|
||||
Some(dleq) => verify_dleq(blinded_message, self.c, &dleq.e, &dleq.s, mint_pubkey),
|
||||
None => Err(Error::MissingDleqProof),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -188,12 +182,8 @@ impl BlindSignature {
|
||||
blinded_message: &PublicKey,
|
||||
mint_secretkey: &SecretKey,
|
||||
) -> Result<(), Error> {
|
||||
let blinded_message: k256::PublicKey = blinded_message.into();
|
||||
let mint_secretkey: k256::SecretKey = mint_secretkey.clone().into();
|
||||
|
||||
let dleq = calculate_dleq(self.c.clone().into(), &blinded_message, &mint_secretkey)?;
|
||||
let dleq: BlindSignatureDleq = calculate_dleq(self.c, blinded_message, mint_secretkey)?;
|
||||
self.dleq = Some(dleq);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -222,7 +212,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
blinded.verify_dleq(&mint_key, &blinded_secret).unwrap()
|
||||
blinded.verify_dleq(mint_key, blinded_secret).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -237,6 +227,6 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(proof.verify_dleq(&a).is_ok());
|
||||
assert!(proof.verify_dleq(a).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,45 @@
|
||||
use std::str::FromStr;
|
||||
//! NUT-13: Deterministic Secrets
|
||||
//!
|
||||
//! <https://github.com/cashubtc/nuts/blob/main/13.md>
|
||||
|
||||
use core::str::FromStr;
|
||||
|
||||
use bip32::{DerivationPath, XPrv};
|
||||
use bip39::Mnemonic;
|
||||
use log::debug;
|
||||
use bitcoin::bip32::{DerivationPath, ExtendedPrivKey};
|
||||
use bitcoin::Network;
|
||||
|
||||
use super::{Id, SecretKey};
|
||||
use crate::error::Error;
|
||||
use crate::secret::Secret;
|
||||
use crate::util::hex;
|
||||
use crate::SECP256K1;
|
||||
|
||||
impl Secret {
|
||||
pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
|
||||
debug!(
|
||||
tracing::debug!(
|
||||
"Deriving secret for {} with count {}",
|
||||
keyset_id.to_string(),
|
||||
counter.to_string()
|
||||
);
|
||||
let path = DerivationPath::from_str(&format!(
|
||||
let path: DerivationPath = DerivationPath::from_str(&format!(
|
||||
"m/129372'/0'/{}'/{}'/0",
|
||||
u64::try_from(keyset_id)?,
|
||||
counter
|
||||
))?;
|
||||
|
||||
let xpriv = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
|
||||
let seed: [u8; 64] = mnemonic.to_seed("");
|
||||
let bip32_root_key = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?;
|
||||
let derived_xpriv = bip32_root_key.derive_priv(&SECP256K1, &path)?;
|
||||
|
||||
Ok(Self(hex::encode(xpriv.private_key().to_bytes())))
|
||||
Ok(Self::new(hex::encode(
|
||||
derived_xpriv.private_key.secret_bytes(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
|
||||
debug!(
|
||||
tracing::debug!(
|
||||
"Deriving key for {} with count {}",
|
||||
keyset_id.to_string(),
|
||||
counter.to_string()
|
||||
@@ -40,11 +50,11 @@ impl SecretKey {
|
||||
counter
|
||||
))?;
|
||||
|
||||
let signing_key = XPrv::derive_from_path(mnemonic.to_seed(""), &path)?;
|
||||
let seed: [u8; 64] = mnemonic.to_seed("");
|
||||
let bip32_root_key = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?;
|
||||
let derived_xpriv = bip32_root_key.derive_priv(&SECP256K1, &path)?;
|
||||
|
||||
let private_key = signing_key.private_key();
|
||||
|
||||
Ok(Self(private_key.into()))
|
||||
Ok(Self::from(derived_xpriv.private_key))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +86,7 @@ mod wallet {
|
||||
let secret = Secret::from_seed(mnemonic, keyset_id, counter)?;
|
||||
let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, counter)?;
|
||||
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
|
||||
|
||||
let amount = if zero_amount { Amount::ZERO } else { amount };
|
||||
|
||||
@@ -85,7 +95,7 @@ mod wallet {
|
||||
let pre_mint = PreMint {
|
||||
blinded_message,
|
||||
secret: secret.clone(),
|
||||
r: r.into(),
|
||||
r,
|
||||
amount,
|
||||
};
|
||||
|
||||
@@ -110,14 +120,14 @@ mod wallet {
|
||||
let secret = Secret::from_seed(mnemonic, keyset_id, i)?;
|
||||
let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, i)?;
|
||||
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
|
||||
|
||||
let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
|
||||
|
||||
let pre_mint = PreMint {
|
||||
blinded_message,
|
||||
secret: secret.clone(),
|
||||
r: r.into(),
|
||||
r,
|
||||
amount: Amount::ZERO,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,35 +1,45 @@
|
||||
//! Secret
|
||||
|
||||
use core::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::secp256k1::rand::{self, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::util::hex;
|
||||
|
||||
/// The secret data that allows spending ecash
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Secret(pub String);
|
||||
pub struct Secret(String);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid secret length: `{0}`")]
|
||||
InvalidLength(u64),
|
||||
#[error("Hex error: `{0}`")]
|
||||
Hex(#[from] hex::FromHexError),
|
||||
#[error(transparent)]
|
||||
Hex(#[from] hex::Error),
|
||||
}
|
||||
|
||||
impl Default for Secret {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::generate()
|
||||
}
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
#[inline]
|
||||
pub fn new<S>(secret: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
Self(secret.into())
|
||||
}
|
||||
|
||||
/// Create secret value
|
||||
/// Generate a new random secret as the recommended 32 byte hex
|
||||
pub fn new() -> Self {
|
||||
use rand::RngCore;
|
||||
|
||||
pub fn generate() -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let mut random_bytes = [0u8; 32];
|
||||
@@ -41,8 +51,14 @@ impl Secret {
|
||||
Self(secret)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.0.clone().into_bytes()
|
||||
self.as_bytes().to_vec()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nut11")]
|
||||
@@ -66,13 +82,13 @@ impl FromStr for Secret {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Secret(s.to_string()))
|
||||
Ok(Self(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Secret {
|
||||
fn to_string(&self) -> String {
|
||||
self.0.clone()
|
||||
impl fmt::Display for Secret {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +131,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_secret_from_str() {
|
||||
let secret = Secret::new();
|
||||
let secret = Secret::generate();
|
||||
|
||||
let secret_str = secret.to_string();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Utilities for serde
|
||||
|
||||
// TODO: remove this module
|
||||
|
||||
pub mod serde_url {
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
@@ -44,91 +46,3 @@ pub mod bytes_base64 {
|
||||
Ok(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod serde_public_key {
|
||||
use k256::PublicKey;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn serialize<S>(pubkey: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let encoded = hex::encode(pubkey.to_sec1_bytes());
|
||||
serializer.serialize_str(&encoded)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let encoded = String::deserialize(deserializer)?;
|
||||
let decoded = hex::decode(encoded).map_err(serde::de::Error::custom)?;
|
||||
if decoded.len().ne(&33) {
|
||||
return Err(serde::de::Error::custom(format!(
|
||||
"Invalid key length: {}",
|
||||
decoded.len()
|
||||
)));
|
||||
}
|
||||
PublicKey::from_sec1_bytes(&decoded).map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
pub mod opt {
|
||||
use k256::PublicKey;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
pub fn serialize<S>(pubkey: &Option<PublicKey>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match pubkey {
|
||||
Some(pubkey) => {
|
||||
let encoded = hex::encode(pubkey.to_sec1_bytes());
|
||||
serializer.serialize_str(&encoded)
|
||||
}
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<PublicKey>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let option_str: Option<String> = Option::deserialize(deserializer)?;
|
||||
|
||||
match option_str {
|
||||
Some(encoded) => {
|
||||
let bytes = hex::decode(encoded).map_err(serde::de::Error::custom)?;
|
||||
let pubkey =
|
||||
PublicKey::from_sec1_bytes(&bytes).map_err(serde::de::Error::custom)?;
|
||||
Ok(Some(pubkey))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod serde_secret_key {
|
||||
use k256::elliptic_curve::generic_array::GenericArray;
|
||||
use k256::SecretKey;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn serialize<S>(seckey: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let encoded = hex::encode(seckey.to_bytes());
|
||||
serializer.serialize_str(&encoded)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let encoded = String::deserialize(deserializer)?;
|
||||
Ok(k256::SecretKey::from_bytes(GenericArray::from_slice(
|
||||
&hex::decode(encoded).map_err(serde::de::Error::custom)?,
|
||||
))
|
||||
.map_err(serde::de::Error::custom))?
|
||||
}
|
||||
}
|
||||
|
||||
147
crates/cashu/src/util/hex.rs
Normal file
147
crates/cashu/src/util/hex.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) 2022-2023 Yuki Kishimoto
|
||||
// Distributed under the MIT software license
|
||||
|
||||
//! Hex
|
||||
|
||||
use core::fmt;
|
||||
|
||||
/// Hex error
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// An invalid character was found
|
||||
InvalidHexCharacter {
|
||||
/// Char
|
||||
c: char,
|
||||
/// Char index
|
||||
index: usize,
|
||||
},
|
||||
/// A hex string's length needs to be even, as two digits correspond to
|
||||
/// one byte.
|
||||
OddLength,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::InvalidHexCharacter { c, index } => {
|
||||
write!(f, "Invalid character {} at position {}", c, index)
|
||||
}
|
||||
Self::OddLength => write!(f, "Odd number of digits"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_digit(num: u8) -> char {
|
||||
if num < 10 {
|
||||
(b'0' + num) as char
|
||||
} else {
|
||||
(b'a' + num - 10) as char
|
||||
}
|
||||
}
|
||||
|
||||
/// Hex encode
|
||||
pub fn encode<T>(data: T) -> String
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
let bytes: &[u8] = data.as_ref();
|
||||
let mut hex: String = String::with_capacity(2 * bytes.len());
|
||||
for byte in bytes.iter() {
|
||||
hex.push(from_digit(byte >> 4));
|
||||
hex.push(from_digit(byte & 0xF));
|
||||
}
|
||||
hex
|
||||
}
|
||||
|
||||
const fn val(c: u8, idx: usize) -> Result<u8, Error> {
|
||||
match c {
|
||||
b'A'..=b'F' => Ok(c - b'A' + 10),
|
||||
b'a'..=b'f' => Ok(c - b'a' + 10),
|
||||
b'0'..=b'9' => Ok(c - b'0'),
|
||||
_ => Err(Error::InvalidHexCharacter {
|
||||
c: c as char,
|
||||
index: idx,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hex decode
|
||||
pub fn decode<T>(hex: T) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
let hex = hex.as_ref();
|
||||
let len = hex.len();
|
||||
|
||||
if len % 2 != 0 {
|
||||
return Err(Error::OddLength);
|
||||
}
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(len / 2);
|
||||
|
||||
for i in (0..len).step_by(2) {
|
||||
let high = val(hex[i], i)?;
|
||||
let low = val(hex[i + 1], i + 1)?;
|
||||
bytes.push(high << 4 | low);
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_encode() {
|
||||
assert_eq!(encode("foobar"), "666f6f626172");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode() {
|
||||
assert_eq!(
|
||||
decode("666f6f626172"),
|
||||
Ok(String::from("foobar").into_bytes())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_invalid_length() {
|
||||
assert_eq!(decode("1").unwrap_err(), Error::OddLength);
|
||||
assert_eq!(decode("666f6f6261721").unwrap_err(), Error::OddLength);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_invalid_char() {
|
||||
assert_eq!(
|
||||
decode("66ag").unwrap_err(),
|
||||
Error::InvalidHexCharacter { c: 'g', index: 3 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(bench)]
|
||||
mod benches {
|
||||
use super::*;
|
||||
use crate::test::{black_box, Bencher};
|
||||
|
||||
const EVENT_JSON: &str = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
|
||||
|
||||
#[bench]
|
||||
pub fn hex_encode(bh: &mut Bencher) {
|
||||
bh.iter(|| {
|
||||
black_box(encode(EVENT_JSON));
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn hex_decode(bh: &mut Bencher) {
|
||||
let h = "7b22636f6e74656e74223a227552757659723538354238304c3672534a69486f63773d3d3f69763d6f68364c5671647359596f6c334a66466e58546250413d3d222c22637265617465645f6174223a313634303833393233352c226964223a2232626531376161333033316264636230303666306663653830633134366465613963316330323638623061663233393862623637333336356336343434643435222c226b696e64223a342c227075626b6579223a2266383663343461326465393564393134396235316336613239616665616262613236346331386532666137633439646539333432346130633536393437373835222c22736967223a226135643932393065663936353930383363343930623330336562376565343133353664383737386666313966326639313737366338646334343433333838613634666663663333366536316166346332356330356163336165393532643163656438383965643635356236373739303839313232326161613135623939666464222c2274616773223a5b5b2270222c2231336164633531316465376531636663663163366237663633363566623561303334343264376263616366353635656135376661373737303931326330323364225d5d7d";
|
||||
bh.iter(|| {
|
||||
black_box(decode(h)).unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
41
crates/cashu/src/util/mod.rs
Normal file
41
crates/cashu/src/util/mod.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
//! Util
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::secp256k1::rand::{self, RngCore};
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use instant::SystemTime;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub mod hex;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
const UNIX_EPOCH: SystemTime = SystemTime::UNIX_EPOCH;
|
||||
|
||||
/// Secp256k1 global context
|
||||
pub static SECP256K1: Lazy<Secp256k1<All>> = Lazy::new(|| {
|
||||
let mut ctx = Secp256k1::new();
|
||||
let mut rng = rand::thread_rng();
|
||||
ctx.randomize(&mut rng);
|
||||
ctx
|
||||
});
|
||||
|
||||
pub fn random_hash() -> Vec<u8> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut random_bytes: [u8; 32] = [0u8; Sha256::LEN];
|
||||
rng.fill_bytes(&mut random_bytes);
|
||||
|
||||
let hash = Sha256::hash(&random_bytes);
|
||||
hash.to_byte_array().to_vec()
|
||||
}
|
||||
|
||||
pub fn unix_time() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
//! Utils
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::hashes::Hash;
|
||||
use rand::prelude::*;
|
||||
|
||||
pub fn random_hash() -> Vec<u8> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut random_bytes = [0u8; Sha256::LEN];
|
||||
rng.fill_bytes(&mut random_bytes);
|
||||
let hash = Sha256::hash(&random_bytes);
|
||||
hash.to_byte_array().to_vec()
|
||||
}
|
||||
|
||||
pub fn unix_time() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|x| x.as_secs())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
Reference in New Issue
Block a user