Wallet dleq (#667)

* feat: Add DLEQ proofs to sqlite db
This commit is contained in:
thesimplekid
2025-03-23 17:32:29 +00:00
committed by GitHub
parent 5ba2699eb7
commit e3570c3e98
3 changed files with 129 additions and 6 deletions

View File

@@ -9,6 +9,7 @@
- cdk: proof matches conditions was not matching payment conditions correctly ([thesimplekid]). - cdk: proof matches conditions was not matching payment conditions correctly ([thesimplekid]).
- cdk: Updating mint_url would remove proofs when we want to keep them ([ok300]). - cdk: Updating mint_url would remove proofs when we want to keep them ([ok300]).
- Wallet: Fix ability to receive cashu tokens that include DLEQ proofs ([ok300]). - Wallet: Fix ability to receive cashu tokens that include DLEQ proofs ([ok300]).
- cdk-sqlite: Wallet was not storing dleq proofs ([thesimplekid]).
### Changed ### Changed
- Updated MSRV to 1.75.0 ([thesimplekid]). - Updated MSRV to 1.75.0 ([thesimplekid]).
- cdk-sqlite: Do not use `UPDATE OR REPLACE` ([crodas]). - cdk-sqlite: Do not use `UPDATE OR REPLACE` ([crodas]).

View File

@@ -0,0 +1,4 @@
-- Migration to add DLEQ proof storage to the proof table
ALTER TABLE proof ADD COLUMN dleq_e BLOB;
ALTER TABLE proof ADD COLUMN dleq_s BLOB;
ALTER TABLE proof ADD COLUMN dleq_r BLOB;

View File

@@ -12,8 +12,8 @@ use cdk_common::nuts::{MeltQuoteState, MintQuoteState};
use cdk_common::secret::Secret; use cdk_common::secret::Secret;
use cdk_common::wallet::{self, MintQuote}; use cdk_common::wallet::{self, MintQuote};
use cdk_common::{ use cdk_common::{
database, Amount, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, Proof, PublicKey, SecretKey, database, Amount, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, Proof, ProofDleq, PublicKey,
SpendingConditions, State, SecretKey, SpendingConditions, State,
}; };
use error::Error; use error::Error;
use sqlx::sqlite::SqliteRow; use sqlx::sqlite::SqliteRow;
@@ -585,8 +585,8 @@ WHERE id=?
sqlx::query( sqlx::query(
r#" r#"
INSERT INTO proof INSERT INTO proof
(y, mint_url, state, spending_condition, unit, amount, keyset_id, secret, c, witness) (y, mint_url, state, spending_condition, unit, amount, keyset_id, secret, c, witness, dleq_e, dleq_s, dleq_r)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(y) DO UPDATE SET ON CONFLICT(y) DO UPDATE SET
mint_url = excluded.mint_url, mint_url = excluded.mint_url,
state = excluded.state, state = excluded.state,
@@ -596,7 +596,10 @@ WHERE id=?
keyset_id = excluded.keyset_id, keyset_id = excluded.keyset_id,
secret = excluded.secret, secret = excluded.secret,
c = excluded.c, c = excluded.c,
witness = excluded.witness witness = excluded.witness,
dleq_e = excluded.dleq_e,
dleq_s = excluded.dleq_s,
dleq_r = excluded.dleq_r
; ;
"#, "#,
) )
@@ -619,6 +622,15 @@ WHERE id=?
.witness .witness
.map(|w| serde_json::to_string(&w).unwrap()), .map(|w| serde_json::to_string(&w).unwrap()),
) )
.bind(
proof.proof.dleq.as_ref().map(|dleq| dleq.e.to_secret_bytes().to_vec()),
)
.bind(
proof.proof.dleq.as_ref().map(|dleq| dleq.s.to_secret_bytes().to_vec()),
)
.bind(
proof.proof.dleq.as_ref().map(|dleq| dleq.r.to_secret_bytes().to_vec()),
)
.execute(&self.pool) .execute(&self.pool)
.await .await
.map_err(Error::from)?; .map_err(Error::from)?;
@@ -871,6 +883,11 @@ fn sqlite_row_to_proof_info(row: &SqliteRow) -> Result<ProofInfo, Error> {
let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?; let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?; let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
// Get DLEQ fields
let row_dleq_e: Option<Vec<u8>> = row.try_get("dleq_e").map_err(Error::from)?;
let row_dleq_s: Option<Vec<u8>> = row.try_get("dleq_s").map_err(Error::from)?;
let row_dleq_r: Option<Vec<u8>> = row.try_get("dleq_r").map_err(Error::from)?;
let y: Vec<u8> = row.try_get("y").map_err(Error::from)?; let y: Vec<u8> = row.try_get("y").map_err(Error::from)?;
let row_mint_url: String = row.try_get("mint_url").map_err(Error::from)?; let row_mint_url: String = row.try_get("mint_url").map_err(Error::from)?;
let row_state: String = row.try_get("state").map_err(Error::from)?; let row_state: String = row.try_get("state").map_err(Error::from)?;
@@ -878,13 +895,25 @@ fn sqlite_row_to_proof_info(row: &SqliteRow) -> Result<ProofInfo, Error> {
row.try_get("spending_condition").map_err(Error::from)?; row.try_get("spending_condition").map_err(Error::from)?;
let row_unit: String = row.try_get("unit").map_err(Error::from)?; let row_unit: String = row.try_get("unit").map_err(Error::from)?;
// Create DLEQ proof if all fields are present
let dleq = match (row_dleq_e, row_dleq_s, row_dleq_r) {
(Some(e), Some(s), Some(r)) => {
let e_key = SecretKey::from_slice(&e)?;
let s_key = SecretKey::from_slice(&s)?;
let r_key = SecretKey::from_slice(&r)?;
Some(ProofDleq::new(e_key, s_key, r_key))
}
_ => None,
};
let proof = Proof { let proof = Proof {
amount: Amount::from(row_amount as u64), amount: Amount::from(row_amount as u64),
keyset_id: Id::from_str(&keyset_id)?, keyset_id: Id::from_str(&keyset_id)?,
secret: Secret::from_str(&row_secret)?, secret: Secret::from_str(&row_secret)?,
c: PublicKey::from_slice(&row_c)?, c: PublicKey::from_slice(&row_c)?,
witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()), witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
dleq: None, dleq,
}; };
Ok(ProofInfo { Ok(ProofInfo {
@@ -899,6 +928,11 @@ fn sqlite_row_to_proof_info(row: &SqliteRow) -> Result<ProofInfo, Error> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use cdk_common::database::WalletDatabase;
use cdk_common::nuts::{ProofDleq, State};
use cdk_common::secret::Secret;
use crate::WalletSqliteDatabase;
#[tokio::test] #[tokio::test]
#[cfg(feature = "sqlcipher")] #[cfg(feature = "sqlcipher")]
@@ -927,4 +961,88 @@ mod tests {
assert_eq!(mint_info, res.clone().unwrap()); assert_eq!(mint_info, res.clone().unwrap());
assert_eq!("test", &res.unwrap().description.unwrap()); assert_eq!("test", &res.unwrap().description.unwrap());
} }
#[tokio::test]
async fn test_proof_with_dleq() {
use std::str::FromStr;
use cdk_common::common::ProofInfo;
use cdk_common::mint_url::MintUrl;
use cdk_common::nuts::{CurrencyUnit, Id, Proof, PublicKey, SecretKey};
use cdk_common::Amount;
// Create a temporary database
let path = std::env::temp_dir()
.to_path_buf()
.join(format!("cdk-test-dleq-{}.sqlite", uuid::Uuid::new_v4()));
#[cfg(feature = "sqlcipher")]
let db = WalletSqliteDatabase::new(path, "password".to_string())
.await
.unwrap();
#[cfg(not(feature = "sqlcipher"))]
let db = WalletSqliteDatabase::new(path).await.unwrap();
db.migrate().await;
// Create a proof with DLEQ
let keyset_id = Id::from_str("00deadbeef123456").unwrap();
let mint_url = MintUrl::from_str("https://example.com").unwrap();
let secret = Secret::new("test_secret_for_dleq");
// Create DLEQ components
let e = SecretKey::generate();
let s = SecretKey::generate();
let r = SecretKey::generate();
let dleq = ProofDleq::new(e.clone(), s.clone(), r.clone());
let mut proof = Proof::new(
Amount::from(64),
keyset_id,
secret,
PublicKey::from_hex(
"02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
)
.unwrap(),
);
// Add DLEQ to the proof
proof.dleq = Some(dleq);
// Create ProofInfo
let proof_info =
ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
// Store the proof in the database
db.update_proofs(vec![proof_info.clone()], vec![])
.await
.unwrap();
// Retrieve the proof from the database
let retrieved_proofs = db
.get_proofs(
Some(mint_url),
Some(CurrencyUnit::Sat),
Some(vec![State::Unspent]),
None,
)
.await
.unwrap();
// Verify we got back exactly one proof
assert_eq!(retrieved_proofs.len(), 1);
// Verify the DLEQ data was preserved
let retrieved_proof = &retrieved_proofs[0];
assert!(retrieved_proof.proof.dleq.is_some());
let retrieved_dleq = retrieved_proof.proof.dleq.as_ref().unwrap();
// Verify DLEQ components match what we stored
assert_eq!(retrieved_dleq.e.to_string(), e.to_string());
assert_eq!(retrieved_dleq.s.to_string(), s.to_string());
assert_eq!(retrieved_dleq.r.to_string(), r.to_string());
}
} }