diff --git a/cashu/core/base.py b/cashu/core/base.py index 7694ff8..00c9a0e 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -7,6 +7,16 @@ from pydantic import BaseModel class P2SHScript(BaseModel): script: str signature: str + address: str = None + + @classmethod + def from_row(cls, row: Row): + return cls( + address=row[0], + script=row[1], + signature=row[2], + used=row[3], + ) class Proof(BaseModel): diff --git a/cashu/wallet/cli.py b/cashu/wallet/cli.py index f29c589..1d825a2 100755 --- a/cashu/wallet/cli.py +++ b/cashu/wallet/cli.py @@ -12,7 +12,6 @@ from itertools import groupby from operator import itemgetter import click -from bech32 import bech32_decode, bech32_encode, convertbits from loguru import logger import cashu.core.bolt11 as bolt11 @@ -20,10 +19,9 @@ from cashu.core.base import Proof from cashu.core.bolt11 import Invoice from cashu.core.helpers import fee_reserve from cashu.core.migrations import migrate_databases -from cashu.core.script import * from cashu.core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL, VERSION, ENV_FILE from cashu.wallet import migrations -from cashu.wallet.crud import get_reserved_proofs +from cashu.wallet.crud import get_reserved_proofs, get_unused_locks from cashu.wallet.wallet import Wallet as Wallet @@ -235,10 +233,11 @@ async def pay(ctx, invoice: str): @click.pass_context @coro async def lock(ctx): - alice_privkey = step0_carol_privkey() - txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub) - txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript) - # print("Redeem script:", txin_redeemScript.__repr__()) + wallet: Wallet = ctx.obj["WALLET"] + p2shscript = await wallet.create_p2sh_lock() + txin_p2sh_address = p2shscript.address + txin_redeemScript_b64 = p2shscript.script + txin_signature_b64 = p2shscript.signature print("---- Pay to script hash (P2SH) ----\n") print("Use a lock to receive coins that only you can unlock.") print("") @@ -248,10 +247,6 @@ async def lock(ctx): f"Send coins to this lock:\n\ncashu send --lock P2SH:{txin_p2sh_address}" ) print("") - - txin_signature = step2_carol_sign_tx(txin_redeemScript, alice_privkey).scriptSig - txin_redeemScript_b64 = base64.urlsafe_b64encode(txin_redeemScript).decode() - txin_signature_b64 = base64.urlsafe_b64encode(txin_signature).decode() print( "!!! The command below is private. Do not share. You have to remember it. Do not lose. !!!\n" ) @@ -260,6 +255,26 @@ async def lock(ctx): ) +@cli.command("locks", help="Show all receiving locks.") +@click.pass_context +@coro +async def locks(ctx): + wallet: Wallet = ctx.obj["WALLET"] + locks = await get_unused_locks(db=wallet.db) + if len(locks): + print("") + print(f"--------------------------\n") + for l in locks: + print(f"Address: {l.address}") + print(f"Script: {l.script}") + print(f"Signature: {l.signature}") + print("") + print(f"Receive: cashu receive --unlock {l.script}:{l.signature}") + print("") + print(f"--------------------------\n") + return True + + @cli.command("info", help="Information about Cashu wallet.") @click.pass_context @coro diff --git a/cashu/wallet/crud.py b/cashu/wallet/crud.py index 0a0e97b..4d212aa 100644 --- a/cashu/wallet/crud.py +++ b/cashu/wallet/crud.py @@ -1,7 +1,7 @@ import time from typing import Optional -from cashu.core.base import Proof +from cashu.core.base import Proof, P2SHScript from cashu.core.db import Connection, Database @@ -113,3 +113,50 @@ async def secret_used( (secret), ) return rows is not None + + +async def store_p2sh( + p2sh: P2SHScript, + db: Database, + conn: Optional[Connection] = None, +): + + await (conn or db).execute( + """ + INSERT INTO p2sh + (address, script, signature, used) + VALUES (?, ?, ?, ?) + """, + (p2sh.address, p2sh.script, p2sh.signature, False), + ) + + +async def get_unused_locks( + db: Database, + conn: Optional[Connection] = None, +): + + rows = await (conn or db).fetchall( + """ + SELECT * from p2sh + WHERE used = 0 + """ + ) + return [P2SHScript.from_row(r) for r in rows] + + +async def update_p2sh_used( + p2sh: P2SHScript, + used: bool, + db: Database = None, + conn: Optional[Connection] = None, +): + clauses = [] + values = [] + clauses.append("used = ?") + values.append(used) + + await (conn or db).execute( + f"UPDATE proofs SET {', '.join(clauses)} WHERE address = ?", + (*values, str(p2sh.address)), + ) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 7f55268..b941869 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -18,6 +18,12 @@ from cashu.core.base import ( Proof, SplitPayload, ) +from cashu.core.script import ( + step0_carol_privkey, + step0_carol_checksig_redeemscrip, + step1_carol_create_p2sh_address, + step2_carol_sign_tx, +) from cashu.core.db import Database from cashu.core.secp import PublicKey from cashu.core.settings import DEBUG @@ -28,6 +34,7 @@ from cashu.wallet.crud import ( secret_used, store_proof, update_proof_reserved, + store_p2sh, ) @@ -194,9 +201,6 @@ class LedgerAPI: return fst_proofs, snd_proofs - async def create_p2sh_lock(self, secrets: List[str]): - return None - async def check_spendable(self, proofs: List[Proof]): payload = CheckPayload(proofs=proofs) return_dict = requests.post( @@ -334,6 +338,21 @@ class Wallet(LedgerAPI): filter(lambda p: p["secret"] not in invalidate_secrets, self.proofs) ) + async def create_p2sh_lock(self): + alice_privkey = step0_carol_privkey() + txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub) + txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript) + txin_signature = step2_carol_sign_tx(txin_redeemScript, alice_privkey).scriptSig + txin_redeemScript_b64 = base64.urlsafe_b64encode(txin_redeemScript).decode() + txin_signature_b64 = base64.urlsafe_b64encode(txin_signature).decode() + p2shScript = P2SHScript( + script=txin_redeemScript_b64, + signature=txin_signature_b64, + address=str(txin_p2sh_address), + ) + await store_p2sh(p2shScript, db=self.db) + return p2shScript + @property def balance(self): return sum(p["amount"] for p in self.proofs)