mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-08 19:24:21 +01:00
multiple sends to address through random secret
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user