multiple sends to address through random secret

This commit is contained in:
callebtc
2022-10-02 22:05:09 +02:00
parent 34dfd24587
commit 46c5ef298d
3 changed files with 87 additions and 60 deletions

View File

@@ -16,9 +16,14 @@ from cashu.core.secp import PrivateKey, PublicKey
from cashu.core.settings import LIGHTNING, MAX_ORDER
from cashu.core.split import amount_split
from cashu.lightning import WALLET
from cashu.mint.crud import (get_lightning_invoice, get_proofs_used,
invalidate_proof, store_lightning_invoice,
store_promise, update_lightning_invoice)
from cashu.mint.crud import (
get_lightning_invoice,
get_proofs_used,
invalidate_proof,
store_lightning_invoice,
store_promise,
update_lightning_invoice,
)
class Ledger:
@@ -104,19 +109,20 @@ class Ledger:
txin_p2sh_address, valid = verify_script(
proof.script.script, proof.script.signature
)
# if len(proof.script) < 16:
# raise Exception("Script error: not long enough.")
# if (
# hashlib.sha256(proof.script.encode("utf-8")).hexdigest()
# != proof.secret.split("P2SH:")[1]
# ):
# raise Exception("Script error: script hash not valid.")
print(
f"Script {proof.script.script.__repr__()} {'valid' if valid else 'invalid'}."
)
if valid:
print(f"{idx}:P2SH:{txin_p2sh_address}")
proof.secret = f"{idx}:P2SH:{txin_p2sh_address}"
# check if secret commits to script address
# format: P2SH:<address>:<secret>
assert len(proof.secret.split(":")) == 3, "secret format wrong"
assert proof.secret.split(":")[1] == str(
txin_p2sh_address
), f"secret does not contain P2SH address: {proof.secret.split(':')[1]}!={txin_p2sh_address}"
# print(
# f"Script {proof.script.script.__repr__()} {'valid' if valid else 'invalid'}."
# )
# if valid:
# print(f"{idx}:P2SH:{txin_p2sh_address}")
# print("proof.secret", proof.secret)
# proof.secret = f"{idx}:P2SH:{txin_p2sh_address}"
return valid
def _verify_outputs(

View File

@@ -101,22 +101,23 @@ async def balance(ctx):
@cli.command("send", help="Send tokens.")
@click.argument("amount", type=int)
@click.option(
"--secret", "-s", default=None, help="Token spending condition.", type=str
)
@click.option("--lock", "-l", default=None, help="Token lock (P2SH address).", type=str)
@click.pass_context
@coro
async def send(ctx, amount: int, secret: str):
if secret and len(secret) < 22:
print("Error: secret has to be at least 22 characters long.")
async def send(ctx, amount: int, lock: str):
if lock and len(lock) < 22:
print("Error: lock has to be at least 22 characters long.")
return
p2sh = False
if len(lock.split("P2SH:")) == 2:
p2sh = True
wallet: Wallet = ctx.obj["WALLET"]
wallet.load_mint()
wallet.status()
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount, secret)
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount, lock)
await wallet.set_reserved(send_proofs, reserved=True)
token = await wallet.serialize_proofs(
send_proofs, hide_secrets=True if secret else False
send_proofs, hide_secrets=True if lock and not p2sh else False
)
print(token)
wallet.status()
@@ -124,19 +125,23 @@ async def send(ctx, amount: int, secret: str):
@cli.command("receive", help="Receive tokens.")
@click.argument("token", type=str)
@click.option("--secret", "-s", default=None, help="Token secret.", type=str)
@click.option("--script", default=None, help="Unlock script.", type=str)
@click.option("--signature", default=None, help="Script signature.", type=str)
# @click.option("--secret", "-s", default=None, help="Token secret.", type=str)
@click.option("--unlock", "-u", default=None, help="Unlock script.", type=str)
# @click.option("--signature", default=None, help="Script signature.", type=str)
@click.pass_context
@coro
async def receive(ctx, token: str, secret: str, script: str, signature: str):
async def receive(ctx, token: str, unlock: str):
wallet: Wallet = ctx.obj["WALLET"]
wallet.load_mint()
wallet.status()
if unlock:
assert (
len(unlock.split(":")) == 2
), "unlock format wrong, expected <script>:<signature>"
script = unlock.split(":")[0]
signature = unlock.split(":")[1]
proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))]
_, _ = await wallet.redeem(
proofs, snd_secret=secret, snd_script=script, snd_siganture=signature
)
_, _ = await wallet.redeem(proofs, snd_script=script, snd_siganture=signature)
wallet.status()
@@ -147,17 +152,26 @@ async def address(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__())
print(f"Receiving address: P2SH:{txin_p2sh_address}")
# print("Redeem script:", txin_redeemScript.__repr__())
print("---- Pay to script hash (P2SH) ----\n")
print("You can use this address to receive tokens that only you can redeem.")
print("")
print(f"Send via command:\ncashu send <amount> --secret P2SH:{txin_p2sh_address}")
print(f"Public receiving address: P2SH:{txin_p2sh_address}")
print("")
print(
f"To send to this address:\n\ncashu send <amount> --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. Back it up. !!!")
print(
f"Receive via command:\ncashu receive <token> --secret P2SH:{txin_p2sh_address} --script {txin_redeemScript_b64} --signature {txin_signature_b64}"
"If you lose this command (script and signature), you will not\nbe able to redeem tokens sent to this address!\n\n"
)
print(
f"To receive:\n\ncashu receive <token> --unlock {txin_redeemScript_b64}:{txin_signature_b64}\n"
)

View File

@@ -8,15 +8,27 @@ import requests
from loguru import logger
import cashu.core.b_dhke as b_dhke
from cashu.core.base import (BlindedMessage, BlindedSignature, CheckPayload,
MeltPayload, MintPayloads, P2SHScript, Proof,
SplitPayload)
from cashu.core.base import (
BlindedMessage,
BlindedSignature,
CheckPayload,
MeltPayload,
MintPayloads,
P2SHScript,
Proof,
SplitPayload,
)
from cashu.core.db import Database
from cashu.core.secp import PublicKey
from cashu.core.settings import DEBUG
from cashu.core.split import amount_split
from cashu.wallet.crud import (get_proofs, invalidate_proof, secret_used,
store_proof, update_proof_reserved)
from cashu.wallet.crud import (
get_proofs,
invalidate_proof,
secret_used,
store_proof,
update_proof_reserved,
)
class LedgerAPI:
@@ -53,7 +65,8 @@ class LedgerAPI:
proofs.append(proof)
return proofs
def _generate_secret(self, randombits=128):
@staticmethod
def _generate_secret(randombits=128):
"""Returns base64 encoded random string."""
return scrts.token_urlsafe(randombits // 8)
@@ -92,9 +105,10 @@ class LedgerAPI:
if await secret_used(s, db=self.db):
raise Exception(f"secret already used: {s}")
@staticmethod
def generate_deterministic_secrets(secret, n):
def generate_secrets(self, secret, n):
"""`secret` is the base string that will be tweaked n times"""
if len(secret.split("P2SH:")) == 2:
return [f"{secret}:{self._generate_secret()}" for i in range(n)]
return [f"{i}:{secret}" for i in range(n)]
async def mint(self, amounts, payment_hash=None):
@@ -138,10 +152,8 @@ class LedgerAPI:
if snd_secret is None:
secrets = [self._generate_secret() for _ in range(len(amounts))]
else:
logger.debug(f"Creating proofs with custom secret: {snd_secret}")
snd_secrets = self.generate_deterministic_secrets(
snd_secret, len(snd_outputs)
)
snd_secrets = self.generate_secrets(snd_secret, len(snd_outputs))
logger.debug(f"Creating proofs with custom secrets: {snd_secrets}")
assert len(snd_secrets) == len(
snd_outputs
), "number of snd_secrets does not match number of ouptus."
@@ -234,34 +246,29 @@ class Wallet(LedgerAPI):
async def redeem(
self,
proofs: List[Proof],
snd_secret: str = None,
# snd_secret: str = None,
snd_script: str = None,
snd_siganture: str = None,
):
if snd_secret:
logger.debug(f"Redeption secret: {snd_secret}")
snd_secrets = self.generate_deterministic_secrets(snd_secret, len(proofs))
assert len(proofs) == len(snd_secrets)
# overload proofs with custom secrets for redemption
for p, s in zip(proofs, snd_secrets):
p.secret = s
has_script = False
# if snd_secret:
# logger.debug(f"Redeption secret: {snd_secret}")
# snd_secrets = self.generate_secrets(snd_secret, len(proofs))
# assert len(proofs) == len(snd_secrets)
# # overload proofs with custom secrets for redemption
# for p, s in zip(proofs, snd_secrets):
# p.secret = s
if snd_script and snd_siganture:
has_script = True
logger.debug(f"Unlock script: {snd_script}")
# overload proofs with unlock script
for p in proofs:
p.script = P2SHScript(script=snd_script, signature=snd_siganture)
return await self.split(
proofs, sum(p["amount"] for p in proofs), has_script=has_script
)
return await self.split(proofs, sum(p["amount"] for p in proofs))
async def split(
self,
proofs: List[Proof],
amount: int,
snd_secret: str = None,
has_script: bool = False,
):
assert len(proofs) > 0, ValueError("no proofs provided.")
fst_proofs, snd_proofs = await super().split(proofs, amount, snd_secret)