mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 02:24:20 +01:00
[Wallet/Mint] DLEQ proofs (#175)
* produce dleq * start working on verification * wip dleq * Use C_ instead of C in verify DLEQ! (#176) * Fix comments (DLEQ sign error) * Fix alice_verify_dleq in d_dhke.py * Fix_generate_promise in ledger.py * Fix verify_proofs_dleq in wallet.py * Fix: invalid public key (#182) * Use C_ instead of C in verify DLEQ! * Fix comments (DLEQ sign error) * Fix alice_verify_dleq in d_dhke.py * Fix_generate_promise in ledger.py * Fix verify_proofs_dleq in wallet.py * Fix: invalid public key * Exception: Mint Error: invalid public key * Update cashu/wallet/wallet.py --------- Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> * Update cashu/core/b_dhke.py * Update tests/test_cli.py * verify all constructed proofs * dleq upon receive * serialize without dleq * all tests passing * make format * remove print * remove debug * option to send with dleq * add tests * fix test * deterministic p in step2_dleq and fix mypy error for hash_to_curve * test crypto/hash_e and crypto/step2_bob_dleq * rename A to K in b_dhke.py and test_alice_verify_dleq * rename tests * make format * store dleq in mint db (and readd balance view) * remove `r` from dleq in tests * add pending output * make format * works with pre-dleq mints * fix comments * make format * fix some tests * fix last test * test serialize dleq fix * flake * flake * keyset.id must be str * fix test decorators * start removing the duplicate fields from the dleq * format * remove print * cleanup * add type anotations to dleq functions * remove unnecessary fields from BlindedSignature * tests not working yet * spelling mistakes * spelling mistakes * fix more spelling mistakes * revert to normal * add comments * bdhke: generalize hash_e * remove P2PKSecret changes * revert tests for P2PKSecret * revert tests * revert test fully * revert p2pksecret changes * refactor proof invalidation * store dleq proofs in wallet db * make mypy happy --------- Co-authored-by: moonsettler <moonsettler@protonmail.com>
This commit is contained in:
@@ -11,6 +11,26 @@ from .crypto.secp import PrivateKey, PublicKey
|
|||||||
from .legacy import derive_keys_backwards_compatible_insecure_pre_0_12
|
from .legacy import derive_keys_backwards_compatible_insecure_pre_0_12
|
||||||
from .p2pk import P2SHScript
|
from .p2pk import P2SHScript
|
||||||
|
|
||||||
|
|
||||||
|
class DLEQ(BaseModel):
|
||||||
|
"""
|
||||||
|
Discrete Log Equality (DLEQ) Proof
|
||||||
|
"""
|
||||||
|
|
||||||
|
e: str
|
||||||
|
s: str
|
||||||
|
|
||||||
|
|
||||||
|
class DLEQWallet(BaseModel):
|
||||||
|
"""
|
||||||
|
Discrete Log Equality (DLEQ) Proof
|
||||||
|
"""
|
||||||
|
|
||||||
|
e: str
|
||||||
|
s: str
|
||||||
|
r: str # blinding_factor, unknown to mint but sent from wallet to wallet for DLEQ proof
|
||||||
|
|
||||||
|
|
||||||
# ------- PROOFS -------
|
# ------- PROOFS -------
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +44,8 @@ class Proof(BaseModel):
|
|||||||
amount: int = 0
|
amount: int = 0
|
||||||
secret: str = "" # secret or message to be blinded and signed
|
secret: str = "" # secret or message to be blinded and signed
|
||||||
C: str = "" # signature on secret, unblinded by wallet
|
C: str = "" # signature on secret, unblinded by wallet
|
||||||
|
dleq: Union[DLEQWallet, None] = None # DLEQ proof
|
||||||
|
|
||||||
p2pksigs: Union[List[str], None] = [] # P2PK signature
|
p2pksigs: Union[List[str], None] = [] # P2PK signature
|
||||||
p2shscript: Union[P2SHScript, None] = None # P2SH spending condition
|
p2shscript: Union[P2SHScript, None] = None # P2SH spending condition
|
||||||
# whether this proof is reserved for sending, used for coin management in the wallet
|
# whether this proof is reserved for sending, used for coin management in the wallet
|
||||||
@@ -34,7 +56,28 @@ class Proof(BaseModel):
|
|||||||
time_reserved: Union[None, str] = ""
|
time_reserved: Union[None, str] = ""
|
||||||
derivation_path: Union[None, str] = "" # derivation path of the proof
|
derivation_path: Union[None, str] = "" # derivation path of the proof
|
||||||
|
|
||||||
def to_dict(self):
|
@classmethod
|
||||||
|
def from_dict(cls, proof_dict: dict):
|
||||||
|
if proof_dict.get("dleq"):
|
||||||
|
proof_dict["dleq"] = DLEQWallet(**json.loads(proof_dict["dleq"]))
|
||||||
|
c = cls(**proof_dict)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def to_dict(self, include_dleq=False):
|
||||||
|
# dictionary without the fields that don't need to be send to Carol
|
||||||
|
if not include_dleq:
|
||||||
|
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C)
|
||||||
|
|
||||||
|
assert self.dleq, "DLEQ proof is missing"
|
||||||
|
return dict(
|
||||||
|
id=self.id,
|
||||||
|
amount=self.amount,
|
||||||
|
secret=self.secret,
|
||||||
|
C=self.C,
|
||||||
|
dleq=self.dleq.dict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict_no_dleq(self):
|
||||||
# dictionary without the fields that don't need to be send to Carol
|
# dictionary without the fields that don't need to be send to Carol
|
||||||
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C)
|
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C)
|
||||||
|
|
||||||
@@ -69,9 +112,10 @@ class BlindedSignature(BaseModel):
|
|||||||
Blinded signature or "promise" which is the signature on a `BlindedMessage`
|
Blinded signature or "promise" which is the signature on a `BlindedMessage`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: Union[str, None] = None
|
id: str
|
||||||
amount: int
|
amount: int
|
||||||
C_: str # Hex-encoded signature
|
C_: str # Hex-encoded signature
|
||||||
|
dleq: Optional[DLEQ] = None # DLEQ proof
|
||||||
|
|
||||||
|
|
||||||
class BlindedMessages(BaseModel):
|
class BlindedMessages(BaseModel):
|
||||||
@@ -296,7 +340,7 @@ class MintKeyset:
|
|||||||
Contains the keyset from the mint's perspective.
|
Contains the keyset from the mint's perspective.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: Union[str, None]
|
id: str
|
||||||
derivation_path: str
|
derivation_path: str
|
||||||
private_keys: Dict[int, PrivateKey]
|
private_keys: Dict[int, PrivateKey]
|
||||||
public_keys: Union[Dict[int, PublicKey], None] = None
|
public_keys: Union[Dict[int, PublicKey], None] = None
|
||||||
@@ -308,7 +352,7 @@ class MintKeyset:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
id=None,
|
id="",
|
||||||
valid_from=None,
|
valid_from=None,
|
||||||
valid_to=None,
|
valid_to=None,
|
||||||
first_seen=None,
|
first_seen=None,
|
||||||
@@ -411,8 +455,8 @@ class TokenV3Token(BaseModel):
|
|||||||
mint: Optional[str] = None
|
mint: Optional[str] = None
|
||||||
proofs: List[Proof]
|
proofs: List[Proof]
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, include_dleq=False):
|
||||||
return_dict = dict(proofs=[p.to_dict() for p in self.proofs])
|
return_dict = dict(proofs=[p.to_dict(include_dleq) for p in self.proofs])
|
||||||
if self.mint:
|
if self.mint:
|
||||||
return_dict.update(dict(mint=self.mint)) # type: ignore
|
return_dict.update(dict(mint=self.mint)) # type: ignore
|
||||||
return return_dict
|
return return_dict
|
||||||
@@ -426,8 +470,8 @@ class TokenV3(BaseModel):
|
|||||||
token: List[TokenV3Token] = []
|
token: List[TokenV3Token] = []
|
||||||
memo: Optional[str] = None
|
memo: Optional[str] = None
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, include_dleq=False):
|
||||||
return_dict = dict(token=[t.to_dict() for t in self.token])
|
return_dict = dict(token=[t.to_dict(include_dleq) for t in self.token])
|
||||||
if self.memo:
|
if self.memo:
|
||||||
return_dict.update(dict(memo=self.memo)) # type: ignore
|
return_dict.update(dict(memo=self.memo)) # type: ignore
|
||||||
return return_dict
|
return return_dict
|
||||||
@@ -454,7 +498,7 @@ class TokenV3(BaseModel):
|
|||||||
token = json.loads(base64.urlsafe_b64decode(token_base64))
|
token = json.loads(base64.urlsafe_b64decode(token_base64))
|
||||||
return cls.parse_obj(token)
|
return cls.parse_obj(token)
|
||||||
|
|
||||||
def serialize(self) -> str:
|
def serialize(self, include_dleq=False) -> str:
|
||||||
"""
|
"""
|
||||||
Takes a TokenV3 and serializes it as "cashuA<json_urlsafe_base64>.
|
Takes a TokenV3 and serializes it as "cashuA<json_urlsafe_base64>.
|
||||||
"""
|
"""
|
||||||
@@ -462,6 +506,6 @@ class TokenV3(BaseModel):
|
|||||||
tokenv3_serialized = prefix
|
tokenv3_serialized = prefix
|
||||||
# encode the token as a base64 string
|
# encode the token as a base64 string
|
||||||
tokenv3_serialized += base64.urlsafe_b64encode(
|
tokenv3_serialized += base64.urlsafe_b64encode(
|
||||||
json.dumps(self.to_dict()).encode()
|
json.dumps(self.to_dict(include_dleq)).encode()
|
||||||
).decode()
|
).decode()
|
||||||
return tokenv3_serialized
|
return tokenv3_serialized
|
||||||
|
|||||||
@@ -28,22 +28,43 @@ Bob:
|
|||||||
Y = hash_to_curve(secret_message)
|
Y = hash_to_curve(secret_message)
|
||||||
C == a*Y
|
C == a*Y
|
||||||
If true, C must have originated from Bob
|
If true, C must have originated from Bob
|
||||||
|
|
||||||
|
|
||||||
|
# DLEQ Proof
|
||||||
|
|
||||||
|
(These steps occur once Bob returns C')
|
||||||
|
|
||||||
|
Bob:
|
||||||
|
r = random nonce
|
||||||
|
R1 = r*G
|
||||||
|
R2 = r*B'
|
||||||
|
e = hash(R1,R2,A,C')
|
||||||
|
s = r + e*a
|
||||||
|
return e, s
|
||||||
|
|
||||||
|
Alice:
|
||||||
|
R1 = s*G - e*A
|
||||||
|
R2 = s*B' - e*C'
|
||||||
|
e == hash(R1,R2,A,C')
|
||||||
|
|
||||||
|
If true, a in A = a*G must be equal to a in C' = a*B'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import Optional
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from secp256k1 import PrivateKey, PublicKey
|
from secp256k1 import PrivateKey, PublicKey
|
||||||
|
|
||||||
|
|
||||||
def hash_to_curve(message: bytes) -> PublicKey:
|
def hash_to_curve(message: bytes) -> PublicKey:
|
||||||
"""Generates a point from the message hash and checks if the point lies on the curve.
|
"""Generates a point from the message hash and checks if the point lies on the curve.
|
||||||
If it does not, it tries computing a new point from the hash."""
|
If it does not, iteratively tries to compute a new point from the hash."""
|
||||||
point = None
|
point = None
|
||||||
msg_to_hash = message
|
msg_to_hash = message
|
||||||
while point is None:
|
while point is None:
|
||||||
_hash = hashlib.sha256(msg_to_hash).digest()
|
_hash = hashlib.sha256(msg_to_hash).digest()
|
||||||
try:
|
try:
|
||||||
|
# will error if point does not lie on curve
|
||||||
point = PublicKey(b"\x02" + _hash, raw=True)
|
point = PublicKey(b"\x02" + _hash, raw=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
msg_to_hash = _hash
|
msg_to_hash = _hash
|
||||||
@@ -59,9 +80,11 @@ def step1_alice(
|
|||||||
return B_, r
|
return B_, r
|
||||||
|
|
||||||
|
|
||||||
def step2_bob(B_: PublicKey, a: PrivateKey) -> PublicKey:
|
def step2_bob(B_: PublicKey, a: PrivateKey) -> Tuple[PublicKey, PrivateKey, PrivateKey]:
|
||||||
C_: PublicKey = B_.mult(a) # type: ignore
|
C_: PublicKey = B_.mult(a) # type: ignore
|
||||||
return C_
|
# produce dleq proof
|
||||||
|
e, s = step2_bob_dleq(B_, a)
|
||||||
|
return C_, e, s
|
||||||
|
|
||||||
|
|
||||||
def step3_alice(C_: PublicKey, r: PrivateKey, A: PublicKey) -> PublicKey:
|
def step3_alice(C_: PublicKey, r: PrivateKey, A: PublicKey) -> PublicKey:
|
||||||
@@ -74,6 +97,61 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool:
|
|||||||
return C == Y.mult(a) # type: ignore
|
return C == Y.mult(a) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def hash_e(*publickeys: PublicKey) -> bytes:
|
||||||
|
e_ = ""
|
||||||
|
for p in publickeys:
|
||||||
|
_p = p.serialize(compressed=False).hex()
|
||||||
|
e_ += str(_p)
|
||||||
|
e = hashlib.sha256(e_.encode("utf-8")).digest()
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
def step2_bob_dleq(
|
||||||
|
B_: PublicKey, a: PrivateKey, p_bytes: bytes = b""
|
||||||
|
) -> Tuple[PrivateKey, PrivateKey]:
|
||||||
|
if p_bytes:
|
||||||
|
# deterministic p for testing
|
||||||
|
p = PrivateKey(privkey=p_bytes, raw=True)
|
||||||
|
else:
|
||||||
|
# normally, we generate a random p
|
||||||
|
p = PrivateKey()
|
||||||
|
|
||||||
|
R1 = p.pubkey # R1 = pG
|
||||||
|
assert R1
|
||||||
|
R2: PublicKey = B_.mult(p) # R2 = pB_ # type: ignore
|
||||||
|
C_: PublicKey = B_.mult(a) # C_ = aB_ # type: ignore
|
||||||
|
A = a.pubkey
|
||||||
|
assert A
|
||||||
|
e = hash_e(R1, R2, A, C_) # e = hash(R1, R2, A, C_)
|
||||||
|
s = p.tweak_add(a.tweak_mul(e)) # s = p + ek
|
||||||
|
spk = PrivateKey(s, raw=True)
|
||||||
|
epk = PrivateKey(e, raw=True)
|
||||||
|
return epk, spk
|
||||||
|
|
||||||
|
|
||||||
|
def alice_verify_dleq(
|
||||||
|
B_: PublicKey, C_: PublicKey, e: PrivateKey, s: PrivateKey, A: PublicKey
|
||||||
|
) -> bool:
|
||||||
|
R1 = s.pubkey - A.mult(e) # type: ignore
|
||||||
|
R2 = B_.mult(s) - C_.mult(e) # type: ignore
|
||||||
|
e_bytes = e.private_key
|
||||||
|
return e_bytes == hash_e(R1, R2, A, C_)
|
||||||
|
|
||||||
|
|
||||||
|
def carol_verify_dleq(
|
||||||
|
secret_msg: str,
|
||||||
|
r: PrivateKey,
|
||||||
|
C: PublicKey,
|
||||||
|
e: PrivateKey,
|
||||||
|
s: PrivateKey,
|
||||||
|
A: PublicKey,
|
||||||
|
) -> bool:
|
||||||
|
Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8"))
|
||||||
|
C_: PublicKey = C + A.mult(r) # type: ignore
|
||||||
|
B_: PublicKey = Y + r.pubkey # type: ignore
|
||||||
|
return alice_verify_dleq(B_, C_, e, s, A)
|
||||||
|
|
||||||
|
|
||||||
# Below is a test of a simple positive and negative case
|
# Below is a test of a simple positive and negative case
|
||||||
|
|
||||||
# # Alice's keys
|
# # Alice's keys
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def step0_carol_privkey():
|
|||||||
return seckey
|
return seckey
|
||||||
|
|
||||||
|
|
||||||
def step0_carol_checksig_redeemscrip(carol_pubkey):
|
def step0_carol_checksig_redeemscript(carol_pubkey):
|
||||||
"""Create script"""
|
"""Create script"""
|
||||||
txin_redeemScript = CScript([carol_pubkey, OP_CHECKSIG])
|
txin_redeemScript = CScript([carol_pubkey, OP_CHECKSIG])
|
||||||
# txin_redeemScript = CScript([-123, OP_CHECKLOCKTIMEVERIFY])
|
# txin_redeemScript = CScript([-123, OP_CHECKLOCKTIMEVERIFY])
|
||||||
@@ -111,7 +111,7 @@ if __name__ == "__main__":
|
|||||||
# ---------
|
# ---------
|
||||||
# CAROL defines scripthash and ALICE mints them
|
# CAROL defines scripthash and ALICE mints them
|
||||||
alice_privkey = step0_carol_privkey()
|
alice_privkey = step0_carol_privkey()
|
||||||
txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub)
|
txin_redeemScript = step0_carol_checksig_redeemscript(alice_privkey.pub)
|
||||||
print("Script:", txin_redeemScript.__repr__())
|
print("Script:", txin_redeemScript.__repr__())
|
||||||
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
|
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
|
||||||
print(f"Carol sends Alice secret = P2SH:{txin_p2sh_address}")
|
print(f"Carol sends Alice secret = P2SH:{txin_p2sh_address}")
|
||||||
@@ -128,7 +128,7 @@ if __name__ == "__main__":
|
|||||||
# CAROL redeems with MINT
|
# CAROL redeems with MINT
|
||||||
|
|
||||||
# CAROL PRODUCES txin_redeemScript and txin_signature to send to MINT
|
# CAROL PRODUCES txin_redeemScript and txin_signature to send to MINT
|
||||||
txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub)
|
txin_redeemScript = step0_carol_checksig_redeemscript(alice_privkey.pub)
|
||||||
txin_signature = step2_carol_sign_tx(txin_redeemScript, alice_privkey).scriptSig
|
txin_signature = step2_carol_sign_tx(txin_redeemScript, alice_privkey).scriptSig
|
||||||
|
|
||||||
txin_redeemScript_b64 = base64.urlsafe_b64encode(txin_redeemScript).decode()
|
txin_redeemScript_b64 = base64.urlsafe_b64encode(txin_redeemScript).decode()
|
||||||
|
|||||||
@@ -49,23 +49,28 @@ class LedgerCrud:
|
|||||||
|
|
||||||
|
|
||||||
async def store_promise(
|
async def store_promise(
|
||||||
|
*,
|
||||||
db: Database,
|
db: Database,
|
||||||
amount: int,
|
amount: int,
|
||||||
B_: str,
|
B_: str,
|
||||||
C_: str,
|
C_: str,
|
||||||
id: str,
|
id: str,
|
||||||
|
e: str = "",
|
||||||
|
s: str = "",
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
):
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'promises')}
|
INSERT INTO {table_with_schema(db, 'promises')}
|
||||||
(amount, B_b, C_b, id)
|
(amount, B_b, C_b, e, s, id)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
amount,
|
amount,
|
||||||
str(B_),
|
B_,
|
||||||
str(C_),
|
C_,
|
||||||
|
e,
|
||||||
|
s,
|
||||||
id,
|
id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from loguru import logger
|
|||||||
|
|
||||||
from ..core import bolt11
|
from ..core import bolt11
|
||||||
from ..core.base import (
|
from ..core.base import (
|
||||||
|
DLEQ,
|
||||||
BlindedMessage,
|
BlindedMessage,
|
||||||
BlindedSignature,
|
BlindedSignature,
|
||||||
Invoice,
|
Invoice,
|
||||||
@@ -117,8 +118,7 @@ class Ledger:
|
|||||||
logger.trace(f"crud: stored new keyset {keyset.id}.")
|
logger.trace(f"crud: stored new keyset {keyset.id}.")
|
||||||
|
|
||||||
# store the new keyset in the current keysets
|
# store the new keyset in the current keysets
|
||||||
if keyset.id:
|
self.keysets.keysets[keyset.id] = keyset
|
||||||
self.keysets.keysets[keyset.id] = keyset
|
|
||||||
logger.debug(f"Loaded keyset {keyset.id}.")
|
logger.debug(f"Loaded keyset {keyset.id}.")
|
||||||
return keyset
|
return keyset
|
||||||
|
|
||||||
@@ -188,17 +188,24 @@ class Ledger:
|
|||||||
keyset = keyset if keyset else self.keyset
|
keyset = keyset if keyset else self.keyset
|
||||||
logger.trace(f"Generating promise with keyset {keyset.id}.")
|
logger.trace(f"Generating promise with keyset {keyset.id}.")
|
||||||
private_key_amount = keyset.private_keys[amount]
|
private_key_amount = keyset.private_keys[amount]
|
||||||
C_ = b_dhke.step2_bob(B_, private_key_amount)
|
C_, e, s = b_dhke.step2_bob(B_, private_key_amount)
|
||||||
logger.trace(f"crud: _generate_promise storing promise for {amount}")
|
logger.trace(f"crud: _generate_promise storing promise for {amount}")
|
||||||
await self.crud.store_promise(
|
await self.crud.store_promise(
|
||||||
amount=amount,
|
amount=amount,
|
||||||
B_=B_.serialize().hex(),
|
B_=B_.serialize().hex(),
|
||||||
C_=C_.serialize().hex(),
|
C_=C_.serialize().hex(),
|
||||||
id=keyset.id,
|
e=e.serialize(),
|
||||||
|
s=s.serialize(),
|
||||||
db=self.db,
|
db=self.db,
|
||||||
|
id=keyset.id,
|
||||||
)
|
)
|
||||||
logger.trace(f"crud: _generate_promise stored promise for {amount}")
|
logger.trace(f"crud: _generate_promise stored promise for {amount}")
|
||||||
return BlindedSignature(id=keyset.id, amount=amount, C_=C_.serialize().hex())
|
return BlindedSignature(
|
||||||
|
id=keyset.id,
|
||||||
|
amount=amount,
|
||||||
|
C_=C_.serialize().hex(),
|
||||||
|
dleq=DLEQ(e=e.serialize(), s=s.serialize()),
|
||||||
|
)
|
||||||
|
|
||||||
def _check_spendable(self, proof: Proof):
|
def _check_spendable(self, proof: Proof):
|
||||||
"""Checks whether the proof was already spent."""
|
"""Checks whether the proof was already spent."""
|
||||||
|
|||||||
@@ -156,3 +156,15 @@ async def m007_proofs_and_promises_store_id(db: Database):
|
|||||||
await db.execute(
|
await db.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN id TEXT"
|
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN id TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m008_promises_dleq(db: Database):
|
||||||
|
"""
|
||||||
|
Add columns for DLEQ proof to promises table.
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN e TEXT"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN s TEXT"
|
||||||
|
)
|
||||||
|
|||||||
@@ -225,11 +225,11 @@ async def send_command(
|
|||||||
global wallet
|
global wallet
|
||||||
if not nostr:
|
if not nostr:
|
||||||
balance, token = await send(
|
balance, token = await send(
|
||||||
wallet, amount, lock, legacy=False, split=not nosplit
|
wallet, amount=amount, lock=lock, legacy=False, split=not nosplit
|
||||||
)
|
)
|
||||||
return SendResponse(balance=balance, token=token)
|
return SendResponse(balance=balance, token=token)
|
||||||
else:
|
else:
|
||||||
token, pubkey = await send_nostr(wallet, amount, nostr)
|
token, pubkey = await send_nostr(wallet, amount=amount, pubkey=nostr)
|
||||||
return SendResponse(balance=wallet.available_balance, token=token, npub=pubkey)
|
return SendResponse(balance=wallet.available_balance, token=token, npub=pubkey)
|
||||||
|
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ async def pending(
|
|||||||
enumerate(
|
enumerate(
|
||||||
groupby(
|
groupby(
|
||||||
sorted_proofs,
|
sorted_proofs,
|
||||||
key=itemgetter("send_id"),
|
key=itemgetter("send_id"), # type: ignore
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
offset,
|
offset,
|
||||||
@@ -334,9 +334,9 @@ async def pending(
|
|||||||
grouped_proofs = list(value)
|
grouped_proofs = list(value)
|
||||||
token = await wallet.serialize_proofs(grouped_proofs)
|
token = await wallet.serialize_proofs(grouped_proofs)
|
||||||
tokenObj = deserialize_token_from_string(token)
|
tokenObj = deserialize_token_from_string(token)
|
||||||
mint = [t.mint for t in tokenObj.token][0]
|
mint = [t.mint for t in tokenObj.token if t.mint][0]
|
||||||
reserved_date = datetime.utcfromtimestamp(
|
reserved_date = datetime.utcfromtimestamp(
|
||||||
int(grouped_proofs[0].time_reserved)
|
int(grouped_proofs[0].time_reserved) # type: ignore
|
||||||
).strftime("%Y-%m-%d %H:%M:%S")
|
).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
result.update(
|
result.update(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -352,6 +352,14 @@ async def balance(ctx: Context, verbose):
|
|||||||
type=str,
|
type=str,
|
||||||
)
|
)
|
||||||
@click.option("--lock", "-l", default=None, help="Lock tokens (P2SH).", type=str)
|
@click.option("--lock", "-l", default=None, help="Lock tokens (P2SH).", type=str)
|
||||||
|
@click.option(
|
||||||
|
"--dleq",
|
||||||
|
"-d",
|
||||||
|
default=False,
|
||||||
|
is_flag=True,
|
||||||
|
help="Send with DLEQ proof.",
|
||||||
|
type=bool,
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--legacy",
|
"--legacy",
|
||||||
"-l",
|
"-l",
|
||||||
@@ -387,6 +395,7 @@ async def send_command(
|
|||||||
nostr: str,
|
nostr: str,
|
||||||
nopt: str,
|
nopt: str,
|
||||||
lock: str,
|
lock: str,
|
||||||
|
dleq: bool,
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
yes: bool,
|
yes: bool,
|
||||||
@@ -394,9 +403,18 @@ async def send_command(
|
|||||||
):
|
):
|
||||||
wallet: Wallet = ctx.obj["WALLET"]
|
wallet: Wallet = ctx.obj["WALLET"]
|
||||||
if not nostr and not nopt:
|
if not nostr and not nopt:
|
||||||
await send(wallet, amount, lock, legacy, split=not nosplit)
|
await send(
|
||||||
|
wallet,
|
||||||
|
amount=amount,
|
||||||
|
lock=lock,
|
||||||
|
legacy=legacy,
|
||||||
|
split=not nosplit,
|
||||||
|
include_dleq=dleq,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await send_nostr(wallet, amount, nostr or nopt, verbose, yes)
|
await send_nostr(
|
||||||
|
wallet, amount=amount, pubkey=nostr or nopt, verbose=verbose, yes=yes
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("receive", help="Receive tokens.")
|
@cli.command("receive", help="Receive tokens.")
|
||||||
@@ -532,14 +550,15 @@ async def pending(ctx: Context, legacy, number: int, offset: int):
|
|||||||
enumerate(
|
enumerate(
|
||||||
groupby(
|
groupby(
|
||||||
sorted_proofs,
|
sorted_proofs,
|
||||||
key=itemgetter("send_id"),
|
key=itemgetter("send_id"), # type: ignore
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
offset,
|
offset,
|
||||||
number,
|
number,
|
||||||
):
|
):
|
||||||
grouped_proofs = list(value)
|
grouped_proofs = list(value)
|
||||||
token = await wallet.serialize_proofs(grouped_proofs)
|
# TODO: we can't return DLEQ because we don't store it
|
||||||
|
token = await wallet.serialize_proofs(grouped_proofs, include_dleq=False)
|
||||||
tokenObj = deserialize_token_from_string(token)
|
tokenObj = deserialize_token_from_string(token)
|
||||||
mint = [t.mint for t in tokenObj.token][0]
|
mint = [t.mint for t in tokenObj.token][0]
|
||||||
# token_hidden_secret = await wallet.serialize_proofs(grouped_proofs)
|
# token_hidden_secret = await wallet.serialize_proofs(grouped_proofs)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import Any, List, Optional, Tuple
|
from typing import Any, List, Optional, Tuple
|
||||||
|
|
||||||
@@ -9,12 +10,12 @@ async def store_proof(
|
|||||||
proof: Proof,
|
proof: Proof,
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO proofs
|
INSERT INTO proofs
|
||||||
(id, amount, C, secret, time_created, derivation_path)
|
(id, amount, C, secret, time_created, derivation_path, dleq)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
proof.id,
|
proof.id,
|
||||||
@@ -23,6 +24,7 @@ async def store_proof(
|
|||||||
str(proof.secret),
|
str(proof.secret),
|
||||||
int(time.time()),
|
int(time.time()),
|
||||||
proof.derivation_path,
|
proof.derivation_path,
|
||||||
|
json.dumps(proof.dleq.dict()) if proof.dleq else "",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,29 +32,29 @@ async def store_proof(
|
|||||||
async def get_proofs(
|
async def get_proofs(
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> List[Proof]:
|
||||||
rows = await (conn or db).fetchall("""
|
rows = await (conn or db).fetchall("""
|
||||||
SELECT * from proofs
|
SELECT * from proofs
|
||||||
""")
|
""")
|
||||||
return [Proof(**dict(r)) for r in rows]
|
return [Proof.from_dict(dict(r)) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
async def get_reserved_proofs(
|
async def get_reserved_proofs(
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> List[Proof]:
|
||||||
rows = await (conn or db).fetchall("""
|
rows = await (conn or db).fetchall("""
|
||||||
SELECT * from proofs
|
SELECT * from proofs
|
||||||
WHERE reserved
|
WHERE reserved
|
||||||
""")
|
""")
|
||||||
return [Proof(**r) for r in rows]
|
return [Proof.from_dict(dict(r)) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
async def invalidate_proof(
|
async def invalidate_proof(
|
||||||
proof: Proof,
|
proof: Proof,
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
DELETE FROM proofs
|
DELETE FROM proofs
|
||||||
@@ -84,7 +86,7 @@ async def update_proof_reserved(
|
|||||||
send_id: str = "",
|
send_id: str = "",
|
||||||
db: Optional[Database] = None,
|
db: Optional[Database] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: List[Any] = []
|
||||||
clauses.append("reserved = ?")
|
clauses.append("reserved = ?")
|
||||||
@@ -109,7 +111,7 @@ async def secret_used(
|
|||||||
secret: str,
|
secret: str,
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> bool:
|
||||||
rows = await (conn or db).fetchone(
|
rows = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * from proofs
|
SELECT * from proofs
|
||||||
@@ -124,7 +126,7 @@ async def store_p2sh(
|
|||||||
p2sh: P2SHScript,
|
p2sh: P2SHScript,
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO p2sh
|
INSERT INTO p2sh
|
||||||
@@ -144,7 +146,7 @@ async def get_unused_locks(
|
|||||||
address: str = "",
|
address: str = "",
|
||||||
db: Optional[Database] = None,
|
db: Optional[Database] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> List[P2SHScript]:
|
||||||
clause: List[str] = []
|
clause: List[str] = []
|
||||||
args: List[str] = []
|
args: List[str] = []
|
||||||
|
|
||||||
@@ -173,7 +175,7 @@ async def update_p2sh_used(
|
|||||||
used: bool,
|
used: bool,
|
||||||
db: Optional[Database] = None,
|
db: Optional[Database] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
clauses = []
|
clauses = []
|
||||||
values = []
|
values = []
|
||||||
clauses.append("used = ?")
|
clauses.append("used = ?")
|
||||||
@@ -190,7 +192,7 @@ async def store_keyset(
|
|||||||
mint_url: str = "",
|
mint_url: str = "",
|
||||||
db: Optional[Database] = None,
|
db: Optional[Database] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute( # type: ignore
|
await (conn or db).execute( # type: ignore
|
||||||
"""
|
"""
|
||||||
INSERT INTO keysets
|
INSERT INTO keysets
|
||||||
@@ -243,7 +245,7 @@ async def store_lightning_invoice(
|
|||||||
db: Database,
|
db: Database,
|
||||||
invoice: Invoice,
|
invoice: Invoice,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO invoices
|
INSERT INTO invoices
|
||||||
@@ -266,7 +268,7 @@ async def get_lightning_invoice(
|
|||||||
db: Database,
|
db: Database,
|
||||||
hash: str = "",
|
hash: str = "",
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> Invoice:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: List[Any] = []
|
||||||
if hash:
|
if hash:
|
||||||
@@ -291,7 +293,7 @@ async def get_lightning_invoices(
|
|||||||
db: Database,
|
db: Database,
|
||||||
paid: Optional[bool] = None,
|
paid: Optional[bool] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> List[Invoice]:
|
||||||
clauses: List[Any] = []
|
clauses: List[Any] = []
|
||||||
values: List[Any] = []
|
values: List[Any] = []
|
||||||
|
|
||||||
@@ -319,7 +321,7 @@ async def update_lightning_invoice(
|
|||||||
paid: bool,
|
paid: bool,
|
||||||
time_paid: Optional[int] = None,
|
time_paid: Optional[int] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: List[Any] = []
|
||||||
clauses.append("paid = ?")
|
clauses.append("paid = ?")
|
||||||
@@ -344,7 +346,7 @@ async def bump_secret_derivation(
|
|||||||
by: int = 1,
|
by: int = 1,
|
||||||
skip: bool = False,
|
skip: bool = False,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> int:
|
||||||
rows = await (conn or db).fetchone(
|
rows = await (conn or db).fetchone(
|
||||||
"SELECT counter from keysets WHERE id = ?", (keyset_id,)
|
"SELECT counter from keysets WHERE id = ?", (keyset_id,)
|
||||||
)
|
)
|
||||||
@@ -374,7 +376,7 @@ async def set_secret_derivation(
|
|||||||
keyset_id: str,
|
keyset_id: str,
|
||||||
counter: int,
|
counter: int,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"UPDATE keysets SET counter = ? WHERE id = ?",
|
"UPDATE keysets SET counter = ? WHERE id = ?",
|
||||||
(
|
(
|
||||||
@@ -388,7 +390,7 @@ async def set_nostr_last_check_timestamp(
|
|||||||
db: Database,
|
db: Database,
|
||||||
timestamp: int,
|
timestamp: int,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"UPDATE nostr SET last = ? WHERE type = ?",
|
"UPDATE nostr SET last = ? WHERE type = ?",
|
||||||
(timestamp, "dm"),
|
(timestamp, "dm"),
|
||||||
@@ -398,7 +400,7 @@ async def set_nostr_last_check_timestamp(
|
|||||||
async def get_nostr_last_check_timestamp(
|
async def get_nostr_last_check_timestamp(
|
||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> Optional[int]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT last from nostr WHERE type = ?
|
SELECT last from nostr WHERE type = ?
|
||||||
@@ -432,7 +434,7 @@ async def store_seed_and_mnemonic(
|
|||||||
seed: str,
|
seed: str,
|
||||||
mnemonic: str,
|
mnemonic: str,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO seed
|
INSERT INTO seed
|
||||||
|
|||||||
@@ -160,7 +160,13 @@ async def receive(
|
|||||||
|
|
||||||
|
|
||||||
async def send(
|
async def send(
|
||||||
wallet: Wallet, amount: int, lock: str, legacy: bool, split: bool = True
|
wallet: Wallet,
|
||||||
|
*,
|
||||||
|
amount: int,
|
||||||
|
lock: str,
|
||||||
|
legacy: bool,
|
||||||
|
split: bool = True,
|
||||||
|
include_dleq: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Prints token to send to stdout.
|
Prints token to send to stdout.
|
||||||
@@ -207,14 +213,14 @@ async def send(
|
|||||||
"No proof with this amount found. Available amounts:"
|
"No proof with this amount found. Available amounts:"
|
||||||
f" {set([p.amount for p in wallet.proofs])}"
|
f" {set([p.amount for p in wallet.proofs])}"
|
||||||
)
|
)
|
||||||
await wallet.set_reserved(send_proofs, reserved=True)
|
|
||||||
|
|
||||||
token = await wallet.serialize_proofs(
|
token = await wallet.serialize_proofs(
|
||||||
send_proofs,
|
send_proofs,
|
||||||
include_mints=True,
|
include_mints=True,
|
||||||
|
include_dleq=include_dleq,
|
||||||
)
|
)
|
||||||
print(token)
|
print(token)
|
||||||
|
await wallet.set_reserved(send_proofs, reserved=True)
|
||||||
if legacy:
|
if legacy:
|
||||||
print("")
|
print("")
|
||||||
print("Old token format:")
|
print("Old token format:")
|
||||||
@@ -222,6 +228,7 @@ async def send(
|
|||||||
token = await wallet.serialize_proofs(
|
token = await wallet.serialize_proofs(
|
||||||
send_proofs,
|
send_proofs,
|
||||||
legacy=True,
|
legacy=True,
|
||||||
|
include_dleq=include_dleq,
|
||||||
)
|
)
|
||||||
print(token)
|
print(token)
|
||||||
|
|
||||||
|
|||||||
@@ -173,3 +173,10 @@ async def m009_privatekey_and_determinstic_key_derivation(db: Database):
|
|||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
# await db.execute("INSERT INTO secret_derivation (counter) VALUES (0)")
|
# await db.execute("INSERT INTO secret_derivation (counter) VALUES (0)")
|
||||||
|
|
||||||
|
|
||||||
|
async def m010_add_proofs_dleq(db: Database):
|
||||||
|
"""
|
||||||
|
Columns to store DLEQ proofs for proofs.
|
||||||
|
"""
|
||||||
|
await db.execute("ALTER TABLE proofs ADD COLUMN dleq TEXT")
|
||||||
|
|||||||
@@ -45,10 +45,12 @@ async def nip5_to_pubkey(wallet: Wallet, address: str):
|
|||||||
|
|
||||||
async def send_nostr(
|
async def send_nostr(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
|
*,
|
||||||
amount: int,
|
amount: int,
|
||||||
pubkey: str,
|
pubkey: str,
|
||||||
verbose: bool = False,
|
verbose: bool = False,
|
||||||
yes: bool = True,
|
yes: bool = True,
|
||||||
|
include_dleq=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Sends tokens via nostr.
|
Sends tokens via nostr.
|
||||||
@@ -62,7 +64,7 @@ async def send_nostr(
|
|||||||
_, send_proofs = await wallet.split_to_send(
|
_, send_proofs = await wallet.split_to_send(
|
||||||
wallet.proofs, amount, set_reserved=True
|
wallet.proofs, amount, set_reserved=True
|
||||||
)
|
)
|
||||||
token = await wallet.serialize_proofs(send_proofs)
|
token = await wallet.serialize_proofs(send_proofs, include_dleq=include_dleq)
|
||||||
|
|
||||||
if pubkey.startswith("npub"):
|
if pubkey.startswith("npub"):
|
||||||
pubkey_to = PublicKey().from_npub(pubkey)
|
pubkey_to = PublicKey().from_npub(pubkey)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from ..core.p2pk import (
|
|||||||
sign_p2pk_sign,
|
sign_p2pk_sign,
|
||||||
)
|
)
|
||||||
from ..core.script import (
|
from ..core.script import (
|
||||||
step0_carol_checksig_redeemscrip,
|
step0_carol_checksig_redeemscript,
|
||||||
step0_carol_privkey,
|
step0_carol_privkey,
|
||||||
step1_carol_create_p2sh_address,
|
step1_carol_create_p2sh_address,
|
||||||
step2_carol_sign_tx,
|
step2_carol_sign_tx,
|
||||||
@@ -41,7 +41,7 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
async def create_p2sh_address_and_store(self) -> str:
|
async def create_p2sh_address_and_store(self) -> str:
|
||||||
"""Creates a P2SH lock script and stores the script and signature in the database."""
|
"""Creates a P2SH lock script and stores the script and signature in the database."""
|
||||||
alice_privkey = step0_carol_privkey()
|
alice_privkey = step0_carol_privkey()
|
||||||
txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub)
|
txin_redeemScript = step0_carol_checksig_redeemscript(alice_privkey.pub)
|
||||||
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
|
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
|
||||||
txin_signature = step2_carol_sign_tx(txin_redeemScript, alice_privkey).scriptSig
|
txin_signature = step2_carol_sign_tx(txin_redeemScript, alice_privkey).scriptSig
|
||||||
txin_redeemScript_b64 = base64.urlsafe_b64encode(txin_redeemScript).decode()
|
txin_redeemScript_b64 = base64.urlsafe_b64encode(txin_redeemScript).decode()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from ..core.base import (
|
|||||||
CheckFeesRequest,
|
CheckFeesRequest,
|
||||||
CheckSpendableRequest,
|
CheckSpendableRequest,
|
||||||
CheckSpendableResponse,
|
CheckSpendableResponse,
|
||||||
|
DLEQWallet,
|
||||||
GetInfoResponse,
|
GetInfoResponse,
|
||||||
GetMeltResponse,
|
GetMeltResponse,
|
||||||
GetMintResponse,
|
GetMintResponse,
|
||||||
@@ -110,101 +111,11 @@ class LedgerAPI(object):
|
|||||||
self.s = requests.Session()
|
self.s = requests.Session()
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
# async def generate_n_secrets(
|
|
||||||
# self, n: int = 1, skip_bump: bool = False
|
|
||||||
# ) -> Tuple[List[str], List[PrivateKey], List[str]]:
|
|
||||||
# return await self.generate_n_secrets(n, skip_bump)
|
|
||||||
|
|
||||||
# async def _generate_secret(self, skip_bump: bool = False) -> str:
|
|
||||||
# return await self._generate_secret(skip_bump)
|
|
||||||
|
|
||||||
@async_set_requests
|
@async_set_requests
|
||||||
async def _init_s(self):
|
async def _init_s(self):
|
||||||
"""Dummy function that can be called from outside to use LedgerAPI.s"""
|
"""Dummy function that can be called from outside to use LedgerAPI.s"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def _construct_proofs(
|
|
||||||
self,
|
|
||||||
promises: List[BlindedSignature],
|
|
||||||
secrets: List[str],
|
|
||||||
rs: List[PrivateKey],
|
|
||||||
derivation_paths: List[str],
|
|
||||||
) -> List[Proof]:
|
|
||||||
"""Constructs proofs from promises, secrets, rs and derivation paths.
|
|
||||||
|
|
||||||
This method is called after the user has received blind signatures from
|
|
||||||
the mint. The results are proofs that can be used as ecash.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
promises (List[BlindedSignature]): blind signatures from mint
|
|
||||||
secrets (List[str]): secrets that were previously used to create blind messages (that turned into promises)
|
|
||||||
rs (List[PrivateKey]): blinding factors that were previously used to create blind messages (that turned into promises)
|
|
||||||
derivation_paths (List[str]): derivation paths that were used to generate secrets and blinding factors
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Proof]: list of proofs that can be used as ecash
|
|
||||||
"""
|
|
||||||
logger.trace("Constructing proofs.")
|
|
||||||
proofs: List[Proof] = []
|
|
||||||
for promise, secret, r, path in zip(promises, secrets, rs, derivation_paths):
|
|
||||||
logger.trace(f"Creating proof with keyset {self.keyset_id} = {promise.id}")
|
|
||||||
assert (
|
|
||||||
self.keyset_id == promise.id
|
|
||||||
), "our keyset id does not match promise id."
|
|
||||||
|
|
||||||
C_ = PublicKey(bytes.fromhex(promise.C_), raw=True)
|
|
||||||
C = b_dhke.step3_alice(C_, r, self.public_keys[promise.amount])
|
|
||||||
|
|
||||||
proof = Proof(
|
|
||||||
id=promise.id,
|
|
||||||
amount=promise.amount,
|
|
||||||
C=C.serialize().hex(),
|
|
||||||
secret=secret,
|
|
||||||
derivation_path=path,
|
|
||||||
)
|
|
||||||
proofs.append(proof)
|
|
||||||
logger.trace(
|
|
||||||
f"Created proof: {proof}, r: {r.serialize()} out of promise {promise}"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.trace(f"Constructed {len(proofs)} proofs.")
|
|
||||||
return proofs
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _construct_outputs(
|
|
||||||
amounts: List[int], secrets: List[str], rs: List[PrivateKey] = []
|
|
||||||
) -> Tuple[List[BlindedMessage], List[PrivateKey]]:
|
|
||||||
"""Takes a list of amounts and secrets and returns outputs.
|
|
||||||
Outputs are blinded messages `outputs` and blinding factors `rs`
|
|
||||||
|
|
||||||
Args:
|
|
||||||
amounts (List[int]): list of amounts
|
|
||||||
secrets (List[str]): list of secrets
|
|
||||||
rs (List[PrivateKey], optional): list of blinding factors. If not given, `rs` are generated in step1_alice. Defaults to [].
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[BlindedMessage]: list of blinded messages that can be sent to the mint
|
|
||||||
List[PrivateKey]: list of blinding factors that can be used to construct proofs after receiving blind signatures from the mint
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
AssertionError: if len(amounts) != len(secrets)
|
|
||||||
"""
|
|
||||||
assert len(amounts) == len(
|
|
||||||
secrets
|
|
||||||
), f"len(amounts)={len(amounts)} not equal to len(secrets)={len(secrets)}"
|
|
||||||
outputs: List[BlindedMessage] = []
|
|
||||||
|
|
||||||
rs_ = [None] * len(amounts) if not rs else rs
|
|
||||||
rs_return: List[PrivateKey] = []
|
|
||||||
for secret, amount, r in zip(secrets, amounts, rs_):
|
|
||||||
B_, r = b_dhke.step1_alice(secret, r or None)
|
|
||||||
rs_return.append(r)
|
|
||||||
output = BlindedMessage(amount=amount, B_=B_.serialize().hex())
|
|
||||||
outputs.append(output)
|
|
||||||
logger.trace(f"Constructing output: {output}, r: {r.serialize()}")
|
|
||||||
|
|
||||||
return outputs, rs_return
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def raise_on_error(resp: Response) -> None:
|
def raise_on_error(resp: Response) -> None:
|
||||||
"""Raises an exception if the response from the mint contains an error.
|
"""Raises an exception if the response from the mint contains an error.
|
||||||
@@ -465,9 +376,9 @@ class LedgerAPI(object):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.raise_on_error(resp)
|
self.raise_on_error(resp)
|
||||||
reponse_dict = resp.json()
|
response_dict = resp.json()
|
||||||
logger.trace("Lightning invoice checked. POST /mint")
|
logger.trace("Lightning invoice checked. POST /mint")
|
||||||
promises = PostMintResponse.parse_obj(reponse_dict).promises
|
promises = PostMintResponse.parse_obj(response_dict).promises
|
||||||
return promises
|
return promises
|
||||||
|
|
||||||
@async_set_requests
|
@async_set_requests
|
||||||
@@ -506,7 +417,7 @@ class LedgerAPI(object):
|
|||||||
@async_set_requests
|
@async_set_requests
|
||||||
async def check_proof_state(self, proofs: List[Proof]):
|
async def check_proof_state(self, proofs: List[Proof]):
|
||||||
"""
|
"""
|
||||||
Cheks whether the secrets in proofs are already spent or not and returns a list of booleans.
|
Checks whether the secrets in proofs are already spent or not and returns a list of booleans.
|
||||||
"""
|
"""
|
||||||
payload = CheckSpendableRequest(proofs=proofs)
|
payload = CheckSpendableRequest(proofs=proofs)
|
||||||
|
|
||||||
@@ -577,8 +488,8 @@ class LedgerAPI(object):
|
|||||||
payload = PostMintRequest(outputs=outputs)
|
payload = PostMintRequest(outputs=outputs)
|
||||||
resp = self.s.post(self.url + "/restore", json=payload.dict())
|
resp = self.s.post(self.url + "/restore", json=payload.dict())
|
||||||
self.raise_on_error(resp)
|
self.raise_on_error(resp)
|
||||||
reponse_dict = resp.json()
|
response_dict = resp.json()
|
||||||
returnObj = PostRestoreResponse.parse_obj(reponse_dict)
|
returnObj = PostRestoreResponse.parse_obj(response_dict)
|
||||||
return returnObj.outputs, returnObj.promises
|
return returnObj.outputs, returnObj.promises
|
||||||
|
|
||||||
|
|
||||||
@@ -609,7 +520,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
super().__init__(url=url, db=self.db)
|
super().__init__(url=url, db=self.db)
|
||||||
logger.debug(f"Wallet initalized with mint URL {url}")
|
logger.debug(f"Wallet initialized with mint URL {url}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def with_db(
|
async def with_db(
|
||||||
@@ -726,16 +637,12 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
await bump_secret_derivation(
|
await bump_secret_derivation(
|
||||||
db=self.db, keyset_id=self.keyset_id, by=len(amounts)
|
db=self.db, keyset_id=self.keyset_id, by=len(amounts)
|
||||||
)
|
)
|
||||||
proofs = self._construct_proofs(promises, secrets, rs, derivation_paths)
|
proofs = await self._construct_proofs(promises, secrets, rs, derivation_paths)
|
||||||
|
|
||||||
if proofs == []:
|
|
||||||
raise Exception("received no proofs.")
|
|
||||||
await self._store_proofs(proofs)
|
|
||||||
if hash:
|
if hash:
|
||||||
await update_lightning_invoice(
|
await update_lightning_invoice(
|
||||||
db=self.db, hash=hash, paid=True, time_paid=int(time.time())
|
db=self.db, hash=hash, paid=True, time_paid=int(time.time())
|
||||||
)
|
)
|
||||||
self.proofs += proofs
|
|
||||||
return proofs
|
return proofs
|
||||||
|
|
||||||
async def redeem(
|
async def redeem(
|
||||||
@@ -749,6 +656,10 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
Args:
|
Args:
|
||||||
proofs (List[Proof]): Proofs to be redeemed.
|
proofs (List[Proof]): Proofs to be redeemed.
|
||||||
"""
|
"""
|
||||||
|
# verify DLEQ of incoming proofs
|
||||||
|
logger.debug("Verifying DLEQ of incoming proofs.")
|
||||||
|
self.verify_proofs_dleq(proofs)
|
||||||
|
logger.debug("DLEQ verified.")
|
||||||
return await self.split(proofs, sum_proofs(proofs))
|
return await self.split(proofs, sum_proofs(proofs))
|
||||||
|
|
||||||
async def split(
|
async def split(
|
||||||
@@ -797,9 +708,9 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
logger.debug(f"Creating proofs with custom secrets: {secret_locks}")
|
logger.debug(f"Creating proofs with custom secrets: {secret_locks}")
|
||||||
assert len(secret_locks) == len(
|
assert len(secret_locks) == len(
|
||||||
scnd_outputs
|
scnd_outputs
|
||||||
), "number of secret_locks does not match number of ouptus."
|
), "number of secret_locks does not match number of outputs."
|
||||||
# append predefined secrets (to send) to random secrets (to keep)
|
# append predefined secrets (to send) to random secrets (to keep)
|
||||||
# generate sercets to keep
|
# generate secrets to keep
|
||||||
secrets = [
|
secrets = [
|
||||||
await self._generate_secret() for s in range(len(frst_outputs))
|
await self._generate_secret() for s in range(len(frst_outputs))
|
||||||
] + secret_locks
|
] + secret_locks
|
||||||
@@ -822,18 +733,11 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
promises = await super().split(proofs, outputs)
|
promises = await super().split(proofs, outputs)
|
||||||
|
|
||||||
# Construct proofs from returned promises (i.e., unblind the signatures)
|
# Construct proofs from returned promises (i.e., unblind the signatures)
|
||||||
new_proofs = self._construct_proofs(promises, secrets, rs, derivation_paths)
|
new_proofs = await self._construct_proofs(
|
||||||
|
promises, secrets, rs, derivation_paths
|
||||||
|
)
|
||||||
|
|
||||||
# remove used proofs from wallet and add new ones
|
await self.invalidate(proofs)
|
||||||
used_secrets = [p.secret for p in proofs]
|
|
||||||
self.proofs = list(filter(lambda p: p.secret not in used_secrets, self.proofs))
|
|
||||||
# add new proofs to wallet
|
|
||||||
self.proofs += new_proofs
|
|
||||||
# store new proofs in database
|
|
||||||
await self._store_proofs(new_proofs)
|
|
||||||
# invalidate used proofs in database
|
|
||||||
for proof in proofs:
|
|
||||||
await invalidate_proof(proof, db=self.db)
|
|
||||||
|
|
||||||
keep_proofs = new_proofs[: len(frst_outputs)]
|
keep_proofs = new_proofs[: len(frst_outputs)]
|
||||||
send_proofs = new_proofs[len(frst_outputs) :]
|
send_proofs = new_proofs[len(frst_outputs) :]
|
||||||
@@ -862,7 +766,6 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
|
|
||||||
if status.paid:
|
if status.paid:
|
||||||
# the payment was successful
|
# the payment was successful
|
||||||
await self.invalidate(proofs)
|
|
||||||
invoice_obj = Invoice(
|
invoice_obj = Invoice(
|
||||||
amount=-sum_proofs(proofs),
|
amount=-sum_proofs(proofs),
|
||||||
pr=invoice,
|
pr=invoice,
|
||||||
@@ -877,14 +780,15 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
|
|
||||||
# handle change and produce proofs
|
# handle change and produce proofs
|
||||||
if status.change:
|
if status.change:
|
||||||
change_proofs = self._construct_proofs(
|
change_proofs = await self._construct_proofs(
|
||||||
status.change,
|
status.change,
|
||||||
secrets[: len(status.change)],
|
secrets[: len(status.change)],
|
||||||
rs[: len(status.change)],
|
rs[: len(status.change)],
|
||||||
derivation_paths[: len(status.change)],
|
derivation_paths[: len(status.change)],
|
||||||
)
|
)
|
||||||
logger.debug(f"Received change: {sum_proofs(change_proofs)} sat")
|
logger.debug(f"Received change: {sum_proofs(change_proofs)} sat")
|
||||||
await self._store_proofs(change_proofs)
|
|
||||||
|
await self.invalidate(proofs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("could not pay invoice.")
|
raise Exception("could not pay invoice.")
|
||||||
@@ -895,10 +799,137 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
|
|
||||||
# ---------- TOKEN MECHANICS ----------
|
# ---------- TOKEN MECHANICS ----------
|
||||||
|
|
||||||
|
# ---------- DLEQ PROOFS ----------
|
||||||
|
|
||||||
|
def verify_proofs_dleq(self, proofs: List[Proof]):
|
||||||
|
"""Verifies DLEQ proofs in proofs."""
|
||||||
|
for proof in proofs:
|
||||||
|
if not proof.dleq:
|
||||||
|
logger.trace("No DLEQ proof in proof.")
|
||||||
|
return
|
||||||
|
logger.trace("Verifying DLEQ proof.")
|
||||||
|
assert self.keys.public_keys
|
||||||
|
if not b_dhke.carol_verify_dleq(
|
||||||
|
secret_msg=proof.secret,
|
||||||
|
C=PublicKey(bytes.fromhex(proof.C), raw=True),
|
||||||
|
r=PrivateKey(bytes.fromhex(proof.dleq.r), raw=True),
|
||||||
|
e=PrivateKey(bytes.fromhex(proof.dleq.e), raw=True),
|
||||||
|
s=PrivateKey(bytes.fromhex(proof.dleq.s), raw=True),
|
||||||
|
A=self.keys.public_keys[proof.amount],
|
||||||
|
):
|
||||||
|
raise Exception("DLEQ proof invalid.")
|
||||||
|
else:
|
||||||
|
logger.debug("DLEQ proof valid.")
|
||||||
|
|
||||||
|
async def _construct_proofs(
|
||||||
|
self,
|
||||||
|
promises: List[BlindedSignature],
|
||||||
|
secrets: List[str],
|
||||||
|
rs: List[PrivateKey],
|
||||||
|
derivation_paths: List[str],
|
||||||
|
) -> List[Proof]:
|
||||||
|
"""Constructs proofs from promises, secrets, rs and derivation paths.
|
||||||
|
|
||||||
|
This method is called after the user has received blind signatures from
|
||||||
|
the mint. The results are proofs that can be used as ecash.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
promises (List[BlindedSignature]): blind signatures from mint
|
||||||
|
secrets (List[str]): secrets that were previously used to create blind messages (that turned into promises)
|
||||||
|
rs (List[PrivateKey]): blinding factors that were previously used to create blind messages (that turned into promises)
|
||||||
|
derivation_paths (List[str]): derivation paths that were used to generate secrets and blinding factors
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Proof]: list of proofs that can be used as ecash
|
||||||
|
"""
|
||||||
|
logger.trace("Constructing proofs.")
|
||||||
|
proofs: List[Proof] = []
|
||||||
|
for promise, secret, r, path in zip(promises, secrets, rs, derivation_paths):
|
||||||
|
logger.trace(f"Creating proof with keyset {self.keyset_id} = {promise.id}")
|
||||||
|
assert (
|
||||||
|
self.keyset_id == promise.id
|
||||||
|
), "our keyset id does not match promise id."
|
||||||
|
|
||||||
|
C_ = PublicKey(bytes.fromhex(promise.C_), raw=True)
|
||||||
|
C = b_dhke.step3_alice(C_, r, self.public_keys[promise.amount])
|
||||||
|
B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs
|
||||||
|
|
||||||
|
proof = Proof(
|
||||||
|
id=promise.id,
|
||||||
|
amount=promise.amount,
|
||||||
|
C=C.serialize().hex(),
|
||||||
|
secret=secret,
|
||||||
|
derivation_path=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
# if the mint returned a dleq proof, we add it to the proof
|
||||||
|
if promise.dleq:
|
||||||
|
proof.dleq = DLEQWallet(
|
||||||
|
e=promise.dleq.e, s=promise.dleq.s, r=r.serialize()
|
||||||
|
)
|
||||||
|
|
||||||
|
proofs.append(proof)
|
||||||
|
|
||||||
|
logger.trace(
|
||||||
|
f"Created proof: {proof}, r: {r.serialize()} out of promise {promise}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# DLEQ verify
|
||||||
|
self.verify_proofs_dleq(proofs)
|
||||||
|
|
||||||
|
logger.trace(f"Constructed {len(proofs)} proofs.")
|
||||||
|
|
||||||
|
# add new proofs to wallet
|
||||||
|
self.proofs += proofs
|
||||||
|
# store new proofs in database
|
||||||
|
await self._store_proofs(proofs)
|
||||||
|
|
||||||
|
return proofs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _construct_outputs(
|
||||||
|
amounts: List[int], secrets: List[str], rs: List[PrivateKey] = []
|
||||||
|
) -> Tuple[List[BlindedMessage], List[PrivateKey]]:
|
||||||
|
"""Takes a list of amounts and secrets and returns outputs.
|
||||||
|
Outputs are blinded messages `outputs` and blinding factors `rs`
|
||||||
|
|
||||||
|
Args:
|
||||||
|
amounts (List[int]): list of amounts
|
||||||
|
secrets (List[str]): list of secrets
|
||||||
|
rs (List[PrivateKey], optional): list of blinding factors. If not given, `rs` are generated in step1_alice. Defaults to [].
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[BlindedMessage]: list of blinded messages that can be sent to the mint
|
||||||
|
List[PrivateKey]: list of blinding factors that can be used to construct proofs after receiving blind signatures from the mint
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: if len(amounts) != len(secrets)
|
||||||
|
"""
|
||||||
|
assert len(amounts) == len(
|
||||||
|
secrets
|
||||||
|
), f"len(amounts)={len(amounts)} not equal to len(secrets)={len(secrets)}"
|
||||||
|
outputs: List[BlindedMessage] = []
|
||||||
|
|
||||||
|
rs_ = [None] * len(amounts) if not rs else rs
|
||||||
|
rs_return: List[PrivateKey] = []
|
||||||
|
for secret, amount, r in zip(secrets, amounts, rs_):
|
||||||
|
B_, r = b_dhke.step1_alice(secret, r or None)
|
||||||
|
rs_return.append(r)
|
||||||
|
output = BlindedMessage(amount=amount, B_=B_.serialize().hex())
|
||||||
|
outputs.append(output)
|
||||||
|
logger.trace(f"Constructing output: {output}, r: {r.serialize()}")
|
||||||
|
|
||||||
|
return outputs, rs_return
|
||||||
|
|
||||||
async def _store_proofs(self, proofs):
|
async def _store_proofs(self, proofs):
|
||||||
async with self.db.connect() as conn:
|
try:
|
||||||
for proof in proofs:
|
async with self.db.connect() as conn: # type: ignore
|
||||||
await store_proof(proof, db=self.db, conn=conn)
|
for proof in proofs:
|
||||||
|
await store_proof(proof, db=self.db, conn=conn)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Could not store proofs in database: {e}")
|
||||||
|
logger.error(proofs)
|
||||||
|
raise e
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_proofs_per_keyset(proofs: List[Proof]):
|
def _get_proofs_per_keyset(proofs: List[Proof]):
|
||||||
@@ -981,7 +1012,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
return token
|
return token
|
||||||
|
|
||||||
async def serialize_proofs(
|
async def serialize_proofs(
|
||||||
self, proofs: List[Proof], include_mints=True, legacy=False
|
self, proofs: List[Proof], include_mints=True, include_dleq=False, legacy=False
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Produces sharable token with proofs and mint information.
|
"""Produces sharable token with proofs and mint information.
|
||||||
|
|
||||||
@@ -1007,7 +1038,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
|
|
||||||
# V3 tokens
|
# V3 tokens
|
||||||
token = await self._make_token(proofs, include_mints)
|
token = await self._make_token(proofs, include_mints)
|
||||||
return token.serialize()
|
return token.serialize(include_dleq)
|
||||||
|
|
||||||
async def _make_token_v2(self, proofs: List[Proof], include_mints=True) -> TokenV2:
|
async def _make_token_v2(self, proofs: List[Proof], include_mints=True) -> TokenV2:
|
||||||
"""
|
"""
|
||||||
@@ -1016,6 +1047,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
"""
|
"""
|
||||||
# build token
|
# build token
|
||||||
token = TokenV2(proofs=proofs)
|
token = TokenV2(proofs=proofs)
|
||||||
|
|
||||||
# add mint information to the token, if requested
|
# add mint information to the token, if requested
|
||||||
if include_mints:
|
if include_mints:
|
||||||
# dummy object to hold information about the mint
|
# dummy object to hold information about the mint
|
||||||
@@ -1132,7 +1164,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
invalidated_proofs = proofs
|
invalidated_proofs = proofs
|
||||||
|
|
||||||
if invalidated_proofs:
|
if invalidated_proofs:
|
||||||
logger.debug(
|
logger.trace(
|
||||||
f"Invalidating {len(invalidated_proofs)} proofs worth"
|
f"Invalidating {len(invalidated_proofs)} proofs worth"
|
||||||
f" {sum_proofs(invalidated_proofs)} sat."
|
f" {sum_proofs(invalidated_proofs)} sat."
|
||||||
)
|
)
|
||||||
@@ -1235,7 +1267,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
mnemonic (Optional[str]): The mnemonic to restore the wallet from. If None, the mnemonic is loaded from the db.
|
mnemonic (Optional[str]): The mnemonic to restore the wallet from. If None, the mnemonic is loaded from the db.
|
||||||
to (int, optional): The number of consecutive empty reponses to stop restoring. Defaults to 2.
|
to (int, optional): The number of consecutive empty responses to stop restoring. Defaults to 2.
|
||||||
batch (int, optional): The number of proofs to restore in one batch. Defaults to 25.
|
batch (int, optional): The number of proofs to restore in one batch. Defaults to 25.
|
||||||
"""
|
"""
|
||||||
await self._init_private_key(mnemonic)
|
await self._init_private_key(mnemonic)
|
||||||
@@ -1295,7 +1327,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
)
|
)
|
||||||
# we don't know the amount but luckily the mint will tell us so we use a dummy amount here
|
# we don't know the amount but luckily the mint will tell us so we use a dummy amount here
|
||||||
amounts_dummy = [1] * len(secrets)
|
amounts_dummy = [1] * len(secrets)
|
||||||
# we generate outptus from deterministic secrets and rs
|
# we generate outputs from deterministic secrets and rs
|
||||||
regenerated_outputs, _ = self._construct_outputs(amounts_dummy, secrets, rs)
|
regenerated_outputs, _ = self._construct_outputs(amounts_dummy, secrets, rs)
|
||||||
# we ask the mint to reissue the promises
|
# we ask the mint to reissue the promises
|
||||||
proofs = await self.restore_promises(
|
proofs = await self.restore_promises(
|
||||||
@@ -1339,14 +1371,8 @@ class Wallet(LedgerAPI, WalletP2PK, WalletSecrets):
|
|||||||
secrets = [secrets[i] for i in matching_indices]
|
secrets = [secrets[i] for i in matching_indices]
|
||||||
rs = [rs[i] for i in matching_indices]
|
rs = [rs[i] for i in matching_indices]
|
||||||
# now we can construct the proofs with the secrets and rs
|
# now we can construct the proofs with the secrets and rs
|
||||||
proofs = self._construct_proofs(
|
proofs = await self._construct_proofs(
|
||||||
restored_promises, secrets, rs, derivation_paths
|
restored_promises, secrets, rs, derivation_paths
|
||||||
)
|
)
|
||||||
logger.debug(f"Restored {len(restored_promises)} promises")
|
logger.debug(f"Restored {len(restored_promises)} promises")
|
||||||
await self._store_proofs(proofs)
|
|
||||||
|
|
||||||
# append proofs to proofs in memory so the balance updates
|
|
||||||
for proof in proofs:
|
|
||||||
if proof.secret not in [p.secret for p in self.proofs]:
|
|
||||||
self.proofs.append(proof)
|
|
||||||
return proofs
|
return proofs
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import asyncio
|
|||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from cashu.core.base import TokenV3
|
||||||
from cashu.core.settings import settings
|
from cashu.core.settings import settings
|
||||||
from cashu.wallet.cli.cli import cli
|
from cashu.wallet.cli.cli import cli
|
||||||
from cashu.wallet.wallet import Wallet
|
from cashu.wallet.wallet import Wallet
|
||||||
@@ -123,9 +124,38 @@ def test_send(mint, cli_prefix):
|
|||||||
[*cli_prefix, "send", "10"],
|
[*cli_prefix, "send", "10"],
|
||||||
)
|
)
|
||||||
assert result.exception is None
|
assert result.exception is None
|
||||||
print("SEND")
|
|
||||||
print(result.output)
|
print(result.output)
|
||||||
assert "cashuA" in result.output, "output does not have a token"
|
token_str = result.output.split("\n")[0]
|
||||||
|
assert "cashuA" in token_str, "output does not have a token"
|
||||||
|
token = TokenV3.deserialize(token_str)
|
||||||
|
assert token.token[0].proofs[0].dleq is None, "dleq included"
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_with_dleq(mint, cli_prefix):
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[*cli_prefix, "send", "10", "--dleq"],
|
||||||
|
)
|
||||||
|
assert result.exception is None
|
||||||
|
print(result.output)
|
||||||
|
token_str = result.output.split("\n")[0]
|
||||||
|
assert "cashuA" in token_str, "output does not have a token"
|
||||||
|
token = TokenV3.deserialize(token_str)
|
||||||
|
assert token.token[0].proofs[0].dleq is not None, "no dleq included"
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_legacy(mint, cli_prefix):
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[*cli_prefix, "send", "10", "--legacy"],
|
||||||
|
)
|
||||||
|
assert result.exception is None
|
||||||
|
print(result.output)
|
||||||
|
# this is the legacy token in the output
|
||||||
|
token_str = result.output.split("\n")[4]
|
||||||
|
assert token_str.startswith("eyJwcm9v"), "output is not as expected"
|
||||||
|
|
||||||
|
|
||||||
def test_send_without_split(mint, cli_prefix):
|
def test_send_without_split(mint, cli_prefix):
|
||||||
@@ -243,3 +273,14 @@ def test_nostr_send(mint, cli_prefix):
|
|||||||
assert result.exception is None
|
assert result.exception is None
|
||||||
print("NOSTR_SEND")
|
print("NOSTR_SEND")
|
||||||
print(result.output)
|
print(result.output)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pending(cli_prefix):
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[*cli_prefix, "pending"],
|
||||||
|
)
|
||||||
|
assert result.exception is None
|
||||||
|
print(result.output)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|||||||
@@ -31,17 +31,79 @@ def test_tokenv3_get_proofs():
|
|||||||
assert len(token.get_proofs()) == 2
|
assert len(token.get_proofs()) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_tokenv3_deserialize_serialize_with_dleq():
|
||||||
|
token_str = (
|
||||||
|
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93M"
|
||||||
|
"SIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjZmZjFiY2VlOGUzMzk2NGE4ZDNjNGQ5NzYwNzdiZ"
|
||||||
|
"DI4ZGVkZWJkODYyMDU0MDQzNDY4ZjU5ZDFiZjI1OTQzN2QiLCAiQyI6ICIwM2I3ZD"
|
||||||
|
"lkMzIzYTAxOWJlNTE4NzRlOGE5OGY1NDViOTg3Y2JmNmU5MWUwMDc1YTFhZjQ3MjY2NDMxOGRlZ"
|
||||||
|
"TQzZTUiLCAiZGxlcSI6IHsiZSI6ICI1ZjkxMGQ4NTc0M2U0OTI0ZjRiNjlkNzhjM"
|
||||||
|
"jFjYTc1ZjEzNzg3Zjc3OTE1NWRmMjMzMjJmYTA1YjU5ODdhYzNmIiwgInMiOiAiZTc4Y2U0MzNiZ"
|
||||||
|
"WNlZTNjNGU1NzM4ZDdjMzRlNDQyZWQ0MmJkMzk0MjI0ZTc3MjE4OGFjMmI5MzZmM"
|
||||||
|
"jA2Y2QxYSIsICJyIjogIjI3MzM3ODNmOTQ4MWZlYzAxNzdlYmM4ZjBhOTI2OWVjOGFkNzU5MDU2ZT"
|
||||||
|
"k3MTRiMWEwYTEwMDQ3MmY2Y2Y5YzIifX0sIHsiaWQiOiAiMWNDTklBWjJYL3cxIi"
|
||||||
|
"wgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiMmFkNDMyZDRkNTg2MzJiMmRlMzI0ZmQxYmE5OTcyZmE"
|
||||||
|
"4MDljNmU3ZGE1ZTkyZWVmYjBiNjYxMmQ5M2Q3ZTAwMCIsICJDIjogIjAzMmFmYjg"
|
||||||
|
"zOWQwMmRmMWNhOGY5ZGZjNTI1NzUxN2Q0MzY4YjdiMTc0MzgzM2JlYWUzZDQzNmExYmQwYmJkYjVk"
|
||||||
|
"OCIsICJkbGVxIjogeyJlIjogImY0NjM2MzU5YTUzZGQxNGEyNmUyNTMyMDQxZWIx"
|
||||||
|
"MDE2OTk1ZTg4NzgwODY0OWFlY2VlNTcwZTA5ZTk2NTU3YzIiLCAicyI6ICJmZWYzMGIzMDcwMDJkMW"
|
||||||
|
"VjNWZiZjg0ZGZhZmRkMGEwOTdkNDJlMDYxNTZiNzdiMTMzMmNjNGZjNGNjYWEyOD"
|
||||||
|
"JmIiwgInIiOiAiODQ5MjQxNzBlYzc3ZjhjMDNmZDRlZTkyZTA3MjdlMzYyNTliZjRhYTc4NTBjZTc2"
|
||||||
|
"NDExMDQ0MmNlNmVlM2FjYyJ9fV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0="
|
||||||
|
)
|
||||||
|
token = TokenV3.deserialize(token_str)
|
||||||
|
assert token.serialize(include_dleq=True) == token_str
|
||||||
|
|
||||||
|
|
||||||
def test_tokenv3_deserialize_serialize():
|
def test_tokenv3_deserialize_serialize():
|
||||||
token_str = (
|
token_str = (
|
||||||
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjVQRjFnNFpWMnci"
|
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJh"
|
||||||
"LCAiQyI6ICIwM2FiNTgwYWQ5NTc3OGVkNTI5NmY4YmVlNjU1ZGJkN2Q2NDJmNWQzMmRlOGUyNDg0NzdlMGI0ZDZhYTg2M2ZjZDUifSwgeyJpZCI6ICJKZWhaTFU2bkNwUmQiLCAiYW"
|
"bW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjVQRjFnNFpWMnci"
|
||||||
"1vdW50IjogOCwgInNlY3JldCI6ICJzNklwZXh3SGNxcXVLZDZYbW9qTDJnIiwgIkMiOiAiMDIyZDAwNGY5ZWMxNmE1OGFkOTAxNGMyNTliNmQ2MTRlZDM2ODgyOWYwMmMzODc3M2M0"
|
"LCAiQyI6ICIwM2FiNTgwYWQ5NTc3OGVkNTI5NmY4YmVlNjU1ZGJkN2Q2NDJmNWQzMmRlOG"
|
||||||
|
"UyNDg0NzdlMGI0ZDZhYTg2M2ZjZDUifSwgeyJpZCI6ICJKZWhaTFU2bkNwUmQiLCAiYW"
|
||||||
|
"1vdW50IjogOCwgInNlY3JldCI6ICJzNklwZXh3SGNxcXVLZDZYbW9qTDJnIiwgIkMiOiAiM"
|
||||||
|
"DIyZDAwNGY5ZWMxNmE1OGFkOTAxNGMyNTliNmQ2MTRlZDM2ODgyOWYwMmMzODc3M2M0"
|
||||||
"NzIyMWY0OTYxY2UzZjIzIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzgifV19"
|
"NzIyMWY0OTYxY2UzZjIzIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzgifV19"
|
||||||
)
|
)
|
||||||
token = TokenV3.deserialize(token_str)
|
token = TokenV3.deserialize(token_str)
|
||||||
assert token.serialize() == token_str
|
assert token.serialize() == token_str
|
||||||
|
|
||||||
|
|
||||||
|
def test_tokenv3_deserialize_serialize_no_dleq():
|
||||||
|
token_str = (
|
||||||
|
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhb"
|
||||||
|
"W91bnQiOiAyLCAic2VjcmV0IjogIjZmZjFiY2VlOGUzMzk2NGE4ZDNjNGQ5NzYwNzdiZ"
|
||||||
|
"DI4ZGVkZWJkODYyMDU0MDQzNDY4ZjU5ZDFiZjI1OTQzN2QiLCAiQyI6ICIwM2I3ZDlkMzIzY"
|
||||||
|
"TAxOWJlNTE4NzRlOGE5OGY1NDViOTg3Y2JmNmU5MWUwMDc1YTFhZjQ3MjY2NDMxOGRlZ"
|
||||||
|
"TQzZTUiLCAiZGxlcSI6IHsiZSI6ICI1ZjkxMGQ4NTc0M2U0OTI0ZjRiNjlkNzhjMjFjYTc1Z"
|
||||||
|
"jEzNzg3Zjc3OTE1NWRmMjMzMjJmYTA1YjU5ODdhYzNmIiwgInMiOiAiZTc4Y2U0MzNiZ"
|
||||||
|
"WNlZTNjNGU1NzM4ZDdjMzRlNDQyZWQ0MmJkMzk0MjI0ZTc3MjE4OGFjMmI5MzZmMjA2Y2QxY"
|
||||||
|
"SIsICJyIjogIjI3MzM3ODNmOTQ4MWZlYzAxNzdlYmM4ZjBhOTI2OWVjOGFkNzU5MDU2ZT"
|
||||||
|
"k3MTRiMWEwYTEwMDQ3MmY2Y2Y5YzIifX0sIHsiaWQiOiAiMWNDTklBWjJYL3cxIiwgImFtb3"
|
||||||
|
"VudCI6IDgsICJzZWNyZXQiOiAiMmFkNDMyZDRkNTg2MzJiMmRlMzI0ZmQxYmE5OTcyZmE"
|
||||||
|
"4MDljNmU3ZGE1ZTkyZWVmYjBiNjYxMmQ5M2Q3ZTAwMCIsICJDIjogIjAzMmFmYjgzOWQwMmR"
|
||||||
|
"mMWNhOGY5ZGZjNTI1NzUxN2Q0MzY4YjdiMTc0MzgzM2JlYWUzZDQzNmExYmQwYmJkYjVk"
|
||||||
|
"OCIsICJkbGVxIjogeyJlIjogImY0NjM2MzU5YTUzZGQxNGEyNmUyNTMyMDQxZWIxMDE2OTk1"
|
||||||
|
"ZTg4NzgwODY0OWFlY2VlNTcwZTA5ZTk2NTU3YzIiLCAicyI6ICJmZWYzMGIzMDcwMDJkMW"
|
||||||
|
"VjNWZiZjg0ZGZhZmRkMGEwOTdkNDJlMDYxNTZiNzdiMTMzMmNjNGZjNGNjYWEyODJmIiwgIn"
|
||||||
|
"IiOiAiODQ5MjQxNzBlYzc3ZjhjMDNmZDRlZTkyZTA3MjdlMzYyNTliZjRhYTc4NTBjZTc2"
|
||||||
|
"NDExMDQ0MmNlNmVlM2FjYyJ9fV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0="
|
||||||
|
)
|
||||||
|
token_str_no_dleq = (
|
||||||
|
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhbW91bn"
|
||||||
|
"QiOiAyLCAic2VjcmV0IjogIjZmZjFiY2VlOGUzMzk2NGE4ZDNjNGQ5NzYwNzdiZDI4"
|
||||||
|
"ZGVkZWJkODYyMDU0MDQzNDY4ZjU5ZDFiZjI1OTQzN2QiLCAiQyI6ICIwM2I3ZDlkMzIzYTAxOWJlN"
|
||||||
|
"TE4NzRlOGE5OGY1NDViOTg3Y2JmNmU5MWUwMDc1YTFhZjQ3MjY2NDMxOGRlZTQzZTU"
|
||||||
|
"ifSwgeyJpZCI6ICIxY0NOSUFaMlgvdzEiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICIyYWQ0MzJkN"
|
||||||
|
"GQ1ODYzMmIyZGUzMjRmZDFiYTk5NzJmYTgwOWM2ZTdkYTVlOTJlZWZiMGI2NjEyZD"
|
||||||
|
"kzZDdlMDAwIiwgIkMiOiAiMDMyYWZiODM5ZDAyZGYxY2E4ZjlkZmM1MjU3NTE3ZDQzNjhiN2IxNzQz"
|
||||||
|
"ODMzYmVhZTNkNDM2YTFiZDBiYmRiNWQ4In1dLCAibWludCI6ICJodHRwOi8vbG9jY"
|
||||||
|
"Wxob3N0OjMzMzgifV19"
|
||||||
|
)
|
||||||
|
token = TokenV3.deserialize(token_str)
|
||||||
|
assert token.serialize(include_dleq=False) == token_str_no_dleq
|
||||||
|
|
||||||
|
|
||||||
def test_tokenv3_deserialize_with_memo():
|
def test_tokenv3_deserialize_with_memo():
|
||||||
token_str = (
|
token_str = (
|
||||||
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjV"
|
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjV"
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
from cashu.core.crypto.b_dhke import hash_to_curve, step1_alice, step2_bob, step3_alice
|
from cashu.core.crypto.b_dhke import (
|
||||||
|
alice_verify_dleq,
|
||||||
|
carol_verify_dleq,
|
||||||
|
hash_e,
|
||||||
|
hash_to_curve,
|
||||||
|
step1_alice,
|
||||||
|
step2_bob,
|
||||||
|
step2_bob_dleq,
|
||||||
|
step3_alice,
|
||||||
|
)
|
||||||
from cashu.core.crypto.secp import PrivateKey, PublicKey
|
from cashu.core.crypto.secp import PrivateKey, PublicKey
|
||||||
|
|
||||||
|
|
||||||
@@ -38,9 +47,9 @@ def test_hash_to_curve_iteration():
|
|||||||
|
|
||||||
|
|
||||||
def test_step1():
|
def test_step1():
|
||||||
""""""
|
secret_msg = "test_message"
|
||||||
B_, blinding_factor = step1_alice(
|
B_, blinding_factor = step1_alice(
|
||||||
"test_message",
|
secret_msg,
|
||||||
blinding_factor=PrivateKey(
|
blinding_factor=PrivateKey(
|
||||||
privkey=bytes.fromhex(
|
privkey=bytes.fromhex(
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001"
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
@@ -73,7 +82,7 @@ def test_step2():
|
|||||||
),
|
),
|
||||||
raw=True,
|
raw=True,
|
||||||
)
|
)
|
||||||
C_ = step2_bob(B_, a)
|
C_, e, s = step2_bob(B_, a)
|
||||||
assert (
|
assert (
|
||||||
C_.serialize().hex()
|
C_.serialize().hex()
|
||||||
== "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"
|
== "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"
|
||||||
@@ -107,3 +116,187 @@ def test_step3():
|
|||||||
C.serialize().hex()
|
C.serialize().hex()
|
||||||
== "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
|
== "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dleq_hash_e():
|
||||||
|
C_ = PublicKey(
|
||||||
|
bytes.fromhex(
|
||||||
|
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
K = PublicKey(
|
||||||
|
pubkey=b"\x02"
|
||||||
|
+ bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
R1 = PublicKey(
|
||||||
|
pubkey=b"\x02"
|
||||||
|
+ bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
R2 = PublicKey(
|
||||||
|
pubkey=b"\x02"
|
||||||
|
+ bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
e = hash_e(R1, R2, K, C_)
|
||||||
|
assert e.hex() == "a4dc034b74338c28c6bc3ea49731f2a24440fc7c4affc08b31a93fc9fbe6401e"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dleq_step2_bob_dleq():
|
||||||
|
B_, _ = step1_alice(
|
||||||
|
"test_message",
|
||||||
|
blinding_factor=PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
a = PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
p_bytes = bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
) # 32 bytes
|
||||||
|
e, s = step2_bob_dleq(B_, a, p_bytes)
|
||||||
|
assert (
|
||||||
|
e.serialize()
|
||||||
|
== "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
s.serialize()
|
||||||
|
== "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"
|
||||||
|
) # differs from e only in least significant byte because `a = 0x1`
|
||||||
|
|
||||||
|
# change `a`
|
||||||
|
a = PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000001111"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
e, s = step2_bob_dleq(B_, a, p_bytes)
|
||||||
|
assert (
|
||||||
|
e.serialize()
|
||||||
|
== "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
s.serialize()
|
||||||
|
== "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dleq_alice_verify_dleq():
|
||||||
|
# e from test_step2_bob_dleq for a=0x1
|
||||||
|
e = PrivateKey(
|
||||||
|
bytes.fromhex(
|
||||||
|
"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
# s from test_step2_bob_dleq for a=0x1
|
||||||
|
s = PrivateKey(
|
||||||
|
bytes.fromhex(
|
||||||
|
"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
a = PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
A = a.pubkey
|
||||||
|
assert A
|
||||||
|
# B_ is the same as we did:
|
||||||
|
# B_, _ = step1_alice(
|
||||||
|
# "test_message",
|
||||||
|
# blinding_factor=bytes.fromhex(
|
||||||
|
# "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
# ), # 32 bytes
|
||||||
|
# )
|
||||||
|
B_ = PublicKey(
|
||||||
|
bytes.fromhex(
|
||||||
|
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# # C_ is the same as if we did:
|
||||||
|
# a = PrivateKey(
|
||||||
|
# privkey=bytes.fromhex(
|
||||||
|
# "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
# ),
|
||||||
|
# raw=True,
|
||||||
|
# )
|
||||||
|
# C_, e, s = step2_bob(B_, a)
|
||||||
|
|
||||||
|
C_ = PublicKey(
|
||||||
|
bytes.fromhex(
|
||||||
|
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert alice_verify_dleq(B_, C_, e, s, A)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dleq_alice_direct_verify_dleq():
|
||||||
|
# ----- test again with B_ and C_ as per step1 and step2
|
||||||
|
a = PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
A = a.pubkey
|
||||||
|
assert A
|
||||||
|
B_, _ = step1_alice(
|
||||||
|
"test_message",
|
||||||
|
blinding_factor=PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
C_, e, s = step2_bob(B_, a)
|
||||||
|
assert alice_verify_dleq(B_, C_, e, s, A)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dleq_carol_varify_from_bob():
|
||||||
|
a = PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
A = a.pubkey
|
||||||
|
assert A
|
||||||
|
secret_msg = "test_message"
|
||||||
|
r = PrivateKey(
|
||||||
|
privkey=bytes.fromhex(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
),
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
B_, _ = step1_alice(secret_msg, r)
|
||||||
|
C_, e, s = step2_bob(B_, a)
|
||||||
|
assert alice_verify_dleq(B_, C_, e, s, A)
|
||||||
|
C = step3_alice(C_, r, A)
|
||||||
|
|
||||||
|
# carol does not know B_ and C_, but she receives C and r from Alice
|
||||||
|
assert carol_verify_dleq(secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A)
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ async def test_generate_promises(ledger: Ledger):
|
|||||||
promises[0].C_
|
promises[0].C_
|
||||||
== "037074c4f53e326ee14ed67125f387d160e0e729351471b69ad41f7d5d21071e15"
|
== "037074c4f53e326ee14ed67125f387d160e0e729351471b69ad41f7d5d21071e15"
|
||||||
)
|
)
|
||||||
|
assert promises[0].amount == 8
|
||||||
|
|
||||||
|
# DLEQ proof present
|
||||||
|
assert promises[0].dleq
|
||||||
|
assert promises[0].dleq.s
|
||||||
|
assert promises[0].dleq.e
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ async def test_mint_amounts_wrong_order(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split(wallet1: Wallet):
|
async def test_split(wallet1: Wallet):
|
||||||
await wallet1.mint(64)
|
await wallet1.mint(64)
|
||||||
|
assert wallet1.balance == 64
|
||||||
p1, p2 = await wallet1.split(wallet1.proofs, 20)
|
p1, p2 = await wallet1.split(wallet1.proofs, 20)
|
||||||
assert wallet1.balance == 64
|
assert wallet1.balance == 64
|
||||||
assert sum_proofs(p1) == 44
|
assert sum_proofs(p1) == 44
|
||||||
|
|||||||
Reference in New Issue
Block a user