From e327d63d16076da204469ab1ce3efb36a1bc0620 Mon Sep 17 00:00:00 2001 From: Rob Woodgate Date: Wed, 13 May 2026 14:23:30 +0100 Subject: [PATCH 1/3] fix v3 Proof.Y hash-to-curve and BlindedSignature dleq=None --- cashu/core/base.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 370126212..4bdfbe321 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -20,6 +20,7 @@ from .crypto.aes import AESCipher from .crypto.b_dhke import hash_to_curve from .crypto.bls import PublicKey as BlsPublicKey +from .crypto.bls_dhke import hash_to_curve as bls_hash_to_curve from .crypto.interfaces import PrivateKey, PublicKey from .crypto.keys import ( derive_keys, @@ -151,7 +152,13 @@ class Proof(BaseModel): def __init__(self, **data): super().__init__(**data) - self.Y = hash_to_curve(self.secret.encode("utf-8")).format().hex() + # v3 (BLS12-381) keysets compute Y on G1; v0/v1/v2 keep secp256k1 hash-to-curve. + # Y is used purely as a lookup index for proofs_used / checkstate / subscriptions, + # so the wallet must compute the same hex for the same (secret, keyset_version). + if self.id and self.id.startswith("02"): + self.Y = bls_hash_to_curve(self.secret.encode("utf-8")).format().hex() + else: + self.Y = hash_to_curve(self.secret.encode("utf-8")).format().hex() @classmethod def from_dict(cls, proof_dict: dict): @@ -271,11 +278,16 @@ class BlindedSignature(BaseModel): @classmethod def from_row(cls, row: Row): + # v3 (BLS) promises store dleq_e / dleq_s as NULL because pairings replace DLEQ. + # Match the model's `dleq: Optional[DLEQ] = None` declaration and skip construction. + dleq_e = row["dleq_e"] + dleq_s = row["dleq_s"] + dleq = DLEQ(e=dleq_e, s=dleq_s) if dleq_e is not None and dleq_s is not None else None return cls( id=row["id"], amount=row["amount"], C_=row["c_"], - dleq=DLEQ(e=row["dleq_e"], s=row["dleq_s"]), + dleq=dleq, ) From 0e8c93f9fe88697adecbf88d494e3833d6f24b2d Mon Sep 17 00:00:00 2001 From: Rob Woodgate Date: Wed, 13 May 2026 14:23:30 +0100 Subject: [PATCH 2/3] fix v3 Proof.Y hash-to-curve and BlindedSignature dleq=None --- cashu/core/base.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 370126212..32c6c4c65 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -20,6 +20,7 @@ from .crypto.aes import AESCipher from .crypto.b_dhke import hash_to_curve from .crypto.bls import PublicKey as BlsPublicKey +from .crypto.bls_dhke import hash_to_curve as bls_hash_to_curve from .crypto.interfaces import PrivateKey, PublicKey from .crypto.keys import ( derive_keys, @@ -151,7 +152,13 @@ class Proof(BaseModel): def __init__(self, **data): super().__init__(**data) - self.Y = hash_to_curve(self.secret.encode("utf-8")).format().hex() + # v3 (BLS12-381) keysets compute Y on G1; v0/v1/v2 keep secp256k1 hash-to-curve. + # Y is used purely as a lookup index for proofs_used / checkstate / subscriptions, + # so the wallet must compute the same hex for the same (secret, keyset_version). + if self.id and is_bls_keyset(self.id): + self.Y = bls_hash_to_curve(self.secret.encode("utf-8")).format().hex() + else: + self.Y = hash_to_curve(self.secret.encode("utf-8")).format().hex() @classmethod def from_dict(cls, proof_dict: dict): @@ -271,11 +278,16 @@ class BlindedSignature(BaseModel): @classmethod def from_row(cls, row: Row): + # v3 (BLS) promises store dleq_e / dleq_s as NULL because pairings replace DLEQ. + # Match the model's `dleq: Optional[DLEQ] = None` declaration and skip construction. + dleq_e = row["dleq_e"] + dleq_s = row["dleq_s"] + dleq = DLEQ(e=dleq_e, s=dleq_s) if dleq_e is not None and dleq_s is not None else None return cls( id=row["id"], amount=row["amount"], C_=row["c_"], - dleq=DLEQ(e=row["dleq_e"], s=row["dleq_s"]), + dleq=dleq, ) From 104789e04d1587aa0234140f98c0ef82fe2a26a1 Mon Sep 17 00:00:00 2001 From: a1denvalu3 Date: Wed, 13 May 2026 15:58:19 +0200 Subject: [PATCH 3/3] concise statement --- cashu/core/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 32c6c4c65..287d07150 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -282,7 +282,7 @@ def from_row(cls, row: Row): # Match the model's `dleq: Optional[DLEQ] = None` declaration and skip construction. dleq_e = row["dleq_e"] dleq_s = row["dleq_s"] - dleq = DLEQ(e=dleq_e, s=dleq_s) if dleq_e is not None and dleq_s is not None else None + dleq = DLEQ(e=dleq_e, s=dleq_s) if None not in (dleq_e, dleq_s) else None return cls( id=row["id"], amount=row["amount"],