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:
Yuki Kishimoto
2024-04-09 20:03:55 +02:00
parent 02fd33225c
commit 3831a4f3bb
32 changed files with 1165 additions and 1030 deletions

View File

@@ -1,5 +1,4 @@
[workspace]
members = [
"crates/cashu",
"crates/cashu-sdk",

View File

@@ -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;

View File

@@ -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(())
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)?;
}
}

View File

@@ -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" ] }

View File

@@ -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());
}
}

View File

@@ -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")]

View File

@@ -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>;

View File

@@ -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

View 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();
});
}
}

View 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.");
}
}

View File

@@ -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);
}

View File

@@ -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};

View File

@@ -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};

View File

@@ -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};

View File

@@ -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};

View File

@@ -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};

View File

@@ -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};

View File

@@ -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};

View File

@@ -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(),

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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,
};

View File

@@ -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();

View File

@@ -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))?
}
}

View 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();
});
}
}

View 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()
}

View File

@@ -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)
}