This commit is contained in:
callebtc
2022-10-02 19:27:14 +02:00
parent a6e655e80a
commit af54161cb3
10 changed files with 179 additions and 117 deletions

View File

@@ -4,11 +4,16 @@ from typing import List
from pydantic import BaseModel from pydantic import BaseModel
class P2SHScript(BaseModel):
script: str
signature: str
class Proof(BaseModel): class Proof(BaseModel):
amount: int amount: int
secret: str = "" secret: str = ""
C: str C: str
script: str = "" script: P2SHScript = None
reserved: bool = False # whether this proof is reserved for sending reserved: bool = False # whether this proof is reserved for sending
send_id: str = "" # unique ID of send attempt send_id: str = "" # unique ID of send attempt
time_created: str = "" time_created: str = ""

View File

@@ -1,7 +1,8 @@
import asyncio import asyncio
from functools import partial, wraps from functools import partial, wraps
from cashu.core.settings import LIGHTNING_FEE_PERCENT, LIGHTNING_RESERVE_FEE_MIN from cashu.core.settings import (LIGHTNING_FEE_PERCENT,
LIGHTNING_RESERVE_FEE_MIN)
def async_wrap(func): def async_wrap(func):

View File

@@ -1,49 +1,38 @@
import hashlib
import base64 import base64
import hashlib
import random
COIN = 100_000_000 COIN = 100_000_000
TXID = "bff785da9f8169f49be92fa95e31f0890c385bfb1bd24d6b94d7900057c617ae" TXID = "bff785da9f8169f49be92fa95e31f0890c385bfb1bd24d6b94d7900057c617ae"
SEED = b"__not__used"
from bitcoin.core import ( from bitcoin.core import (CMutableTxIn, CMutableTxOut, COutPoint, CTransaction,
lx, lx)
COutPoint,
CMutableTxOut,
CMutableTxIn,
CMutableTransaction,
)
from bitcoin.core.script import * from bitcoin.core.script import *
from bitcoin.core.scripteval import VerifyScript from bitcoin.core.scripteval import (SCRIPT_VERIFY_P2SH, EvalScriptError,
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret VerifyScript, VerifyScriptError)
from bitcoin.wallet import CBitcoinSecret, P2SHBitcoinAddress
def step0_carol_privkey(): def step0_carol_privkey():
"""Private key""" """Private key"""
h = hashlib.sha256(b"correct horse battery staple").digest() # h = hashlib.sha256(SEED).digest()
h = hashlib.sha256(str(random.getrandbits(256)).encode()).digest()
seckey = CBitcoinSecret.from_secret_bytes(h) seckey = CBitcoinSecret.from_secret_bytes(h)
return seckey return seckey
def step0_carolt_checksig_redeemscrip(carol_pubkey): def step0_carol_checksig_redeemscrip(carol_pubkey):
"""Create script""" """Create script"""
txin_redeemScript = CScript([carol_pubkey, OP_CHECKSIG]) txin_redeemScript = CScript([carol_pubkey, OP_CHECKSIG])
# txin_redeemScript = CScript( # txin_redeemScript = CScript([-123, OP_CHECKLOCKTIMEVERIFY])
# [ # txin_redeemScript = CScript([3, 3, OP_LESSTHAN, OP_VERIFY])
# 3,
# 3,
# OP_LESSTHANOREQUAL,
# OP_VERIFY,
# ]
# )
return txin_redeemScript return txin_redeemScript
def step1_carol_create_p2sh_address(txin_redeemScript): def step1_carol_create_p2sh_address(txin_redeemScript):
"""Create address (serialized scriptPubKey) to share with Alice""" """Create address (serialized scriptPubKey) to share with Alice"""
# print("Script:", b2x(txin_redeemScript)) txin_p2sh_address = P2SHBitcoinAddress.from_redeemScript(txin_redeemScript)
# returns [OP_HASH160, bitcointx.core.Hash160(self), OP_EQUAL]
txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey()
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey)
# print("Pay to:", str(txin_p2sh_address))
return txin_p2sh_address return txin_p2sh_address
@@ -54,49 +43,81 @@ def step1_bob_carol_create_tx(txin_p2sh_address):
txin = CMutableTxIn(COutPoint(txid, vout)) txin = CMutableTxIn(COutPoint(txid, vout))
txout = CMutableTxOut( txout = CMutableTxOut(
int(0.0005 * COIN), int(0.0005 * COIN),
CBitcoinAddress(str(txin_p2sh_address)).to_scriptPubKey(), P2SHBitcoinAddress(str(txin_p2sh_address)).to_scriptPubKey(),
) )
tx = CMutableTransaction([txin], [txout]) tx = CTransaction([txin], [txout])
return tx, txin return tx, txin
def step2_carol_sign_tx(txin_redeemScript): def step2_carol_sign_tx(txin_redeemScript, privatekey):
"""Sign transaction with private key""" """Sign transaction with private key"""
seckey = step0_carol_privkey()
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript) txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
tx, txin = step1_bob_carol_create_tx(txin_p2sh_address) tx, txin = step1_bob_carol_create_tx(txin_p2sh_address)
sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL) sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL)
sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) sig = privatekey.sign(sighash) + bytes([SIGHASH_ALL])
txin.scriptSig = CScript([sig, txin_redeemScript]) txin.scriptSig = CScript([sig, txin_redeemScript])
return txin return txin
def step3_bob_verify_script(txin_signature, txin_redeemScript): def step3_bob_verify_script(txin_signature, txin_redeemScript, tx):
txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey()
try: try:
VerifyScript(txin_signature, txin_scriptPubKey, tx, 0) VerifyScript(
txin_signature, txin_scriptPubKey, tx, 0, flags=[SCRIPT_VERIFY_P2SH]
)
return True return True
except VerifyScriptError as e:
print("Could not verify script:", e)
except EvalScriptError as e:
print("Script did not evaluate:", e)
print(f"Script: {txin_scriptPubKey.__repr__()}")
except Exception as e: except Exception as e:
print(e) print(e)
return False return False
def verify_script(txin_redeemScript_b64, txin_signature_b64):
txin_redeemScript = CScript(base64.urlsafe_b64decode(txin_redeemScript_b64))
print("Redeem script:", txin_redeemScript.__repr__())
# txin_redeemScript = CScript([2, 3, OP_LESSTHAN, OP_VERIFY])
txin_signature = CScript(value=base64.urlsafe_b64decode(txin_signature_b64))
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
print(f"Bob recreates secret: P2SH:{txin_p2sh_address}")
# MINT checks that P2SH:txin_p2sh_address has not been spent yet
# ...
tx, _ = step1_bob_carol_create_tx(txin_p2sh_address)
print(
f"Bob verifies:\nscript: {txin_redeemScript_b64}\nsignature: {txin_signature_b64}\n"
)
script_valid = step3_bob_verify_script(txin_signature, txin_redeemScript, tx)
# MINT redeems tokens and stores P2SH:txin_p2sh_address
# ...
if script_valid:
print("Successfull.")
else:
print("Error.")
return txin_p2sh_address, script_valid
# simple test case
if __name__ == "__main__": if __name__ == "__main__":
# https://github.com/romanz/python-bitcointx/blob/master/examples/spend-p2sh-txout.py # https://github.com/romanz/python-bitcointx/blob/master/examples/spend-p2sh-txout.py
# CAROL shares txin_p2sh_address with ALICE: # CAROL shares txin_p2sh_address with ALICE:
# --------- # ---------
# CAROL defines scripthash and ALICE mints them # CAROL defines scripthash and ALICE mints them
alice_privkey = step0_carol_privkey()
txin_redeemScript = step0_carolt_checksig_redeemscrip(step0_carol_privkey().pub) txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub)
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}")
print("") print("")
# --------- # ---------
# ALICE: mint tokens with secret SCRIPT:txin_p2sh_address # ALICE: mint tokens with secret P2SH:txin_p2sh_address
print(f"Alice mints tokens with secret = P2SH:{txin_p2sh_address}") print(f"Alice mints tokens with secret = P2SH:{txin_p2sh_address}")
print("") print("")
# ... # ...
@@ -105,13 +126,13 @@ 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_carolt_checksig_redeemscrip(step0_carol_privkey().pub) txin_redeemScript = step0_carol_checksig_redeemscrip(alice_privkey.pub)
txin_signature = step2_carol_sign_tx(txin_redeemScript).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()
txin_signature_b64 = base64.urlsafe_b64encode(txin_signature).decode() txin_signature_b64 = base64.urlsafe_b64encode(txin_signature).decode()
print( print(
f"Carol to Bob:\nscript: {txin_redeemScript_b64}\nsignature: {txin_signature_b64}\n" f"Carol to Bob:\nscript: {txin_redeemScript.__repr__()}\nscript: {txin_redeemScript_b64}\nsignature: {txin_signature_b64}\n"
) )
print("") print("")
# --------- # ---------
@@ -119,20 +140,25 @@ if __name__ == "__main__":
# MINT receives txin_redeemScript_b64 and txin_signature_b64 fom CAROL: # MINT receives txin_redeemScript_b64 and txin_signature_b64 fom CAROL:
txin_redeemScript = CScript(base64.urlsafe_b64decode(txin_redeemScript_b64)) txin_redeemScript = CScript(base64.urlsafe_b64decode(txin_redeemScript_b64))
txin_signature = CScript(base64.urlsafe_b64decode(txin_signature_b64)) txin_redeemScript_p2sh = txin_p2sh_address.to_redeemScript()
print("Redeem script:", txin_redeemScript.__repr__())
print("P2SH:", txin_redeemScript_p2sh.__repr__())
# txin_redeemScript = CScript([2, 3, OP_LESSTHAN, OP_VERIFY])
txin_signature = CScript(value=base64.urlsafe_b64decode(txin_signature_b64))
txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript) txin_p2sh_address = step1_carol_create_p2sh_address(txin_redeemScript)
print(f"Bob recreates secret: P2SH:{txin_p2sh_address}") print(f"Bob recreates secret: P2SH:{txin_p2sh_address}")
# MINT checks that SCRIPT:txin_p2sh_address has not been spent yet # MINT checks that P2SH:txin_p2sh_address has not been spent yet
# ... # ...
tx, _ = step1_bob_carol_create_tx(txin_p2sh_address) tx, _ = step1_bob_carol_create_tx(txin_p2sh_address)
print( print(
f"Bob verifies:\nscript: {txin_redeemScript_b64}\nsignature: {txin_signature_b64}\n" f"Bob verifies:\nscript: {txin_redeemScript_b64}\nsignature: {txin_signature_b64}\n"
) )
script_valid = step3_bob_verify_script(txin_signature, txin_redeemScript) script_valid = step3_bob_verify_script(txin_signature, txin_redeemScript, tx)
# MINT redeems tokens and stores SCRIPT:txin_p2sh_address # MINT redeems tokens and stores P2SH:txin_p2sh_address
# ... # ...
if script_valid:
print("Successfull.") print("Successfull.")
# print("Transaction:", b2x(tx.serialize())) else:
print("Error.")

View File

@@ -8,13 +8,8 @@ import requests
from cashu.core.settings import LNBITS_ENDPOINT, LNBITS_KEY from cashu.core.settings import LNBITS_ENDPOINT, LNBITS_KEY
from .base import ( from .base import (InvoiceResponse, PaymentResponse, PaymentStatus,
InvoiceResponse, StatusResponse, Wallet)
PaymentResponse,
PaymentStatus,
StatusResponse,
Wallet,
)
class LNbitsWallet(Wallet): class LNbitsWallet(Wallet):

View File

@@ -5,7 +5,7 @@ import sys
from fastapi import FastAPI from fastapi import FastAPI
from loguru import logger from loguru import logger
from cashu.core.settings import VERSION, DEBUG from cashu.core.settings import DEBUG, VERSION
from cashu.lightning import WALLET from cashu.lightning import WALLET
from cashu.mint.migrations import m001_initial from cashu.mint.migrations import m001_initial

View File

@@ -3,24 +3,22 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
""" """
import hashlib import hashlib
from inspect import signature
from signal import signal
from typing import List, Set from typing import List, Set
import cashu.core.b_dhke as b_dhke import cashu.core.b_dhke as b_dhke
from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Proof from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Proof
from cashu.core.db import Database from cashu.core.db import Database
from cashu.core.helpers import fee_reserve from cashu.core.helpers import fee_reserve
from cashu.core.script import verify_script
from cashu.core.secp import PrivateKey, PublicKey from cashu.core.secp import PrivateKey, PublicKey
from cashu.core.settings import LIGHTNING, MAX_ORDER from cashu.core.settings import LIGHTNING, MAX_ORDER
from cashu.core.split import amount_split from cashu.core.split import amount_split
from cashu.lightning import WALLET from cashu.lightning import WALLET
from cashu.mint.crud import ( from cashu.mint.crud import (get_lightning_invoice, get_proofs_used,
get_lightning_invoice, invalidate_proof, store_lightning_invoice,
get_proofs_used, store_promise, update_lightning_invoice)
invalidate_proof,
store_lightning_invoice,
store_promise,
update_lightning_invoice,
)
class Ledger: class Ledger:
@@ -73,6 +71,11 @@ class Ledger:
"""Checks whether the proof was already spent.""" """Checks whether the proof was already spent."""
return not proof.secret in self.proofs_used return not proof.secret in self.proofs_used
def _verify_secret_or_script(self, proof: Proof):
if proof.secret and proof.script:
raise Exception("secret and script present at the same time.")
return True
def _verify_secret_criteria(self, proof: Proof): def _verify_secret_criteria(self, proof: Proof):
if proof.secret is None or proof.secret == "": if proof.secret is None or proof.secret == "":
raise Exception("no secret in proof.") raise Exception("no secret in proof.")
@@ -86,23 +89,35 @@ class Ledger:
C = PublicKey(bytes.fromhex(proof.C), raw=True) C = PublicKey(bytes.fromhex(proof.C), raw=True)
return b_dhke.verify(secret_key, C, proof.secret) return b_dhke.verify(secret_key, C, proof.secret)
def _verify_script(self, proof: Proof): def _verify_script(self, idx: int, proof: Proof):
print(f"secret: {proof.secret}")
print(f"script: {proof.script}")
print(
f"script_hash: {hashlib.sha256(proof.script.encode('utf-8')).hexdigest()}"
)
if len(proof.secret.split("SCRIPT:")) != 2:
return True
if len(proof.script) < 16:
raise Exception("Script error: not long enough.")
if ( if (
hashlib.sha256(proof.script.encode("utf-8")).hexdigest() proof.script is None
!= proof.secret.split("SCRIPT:")[1] or proof.script.script is None
or proof.script.signature is None
): ):
raise Exception("Script error: script hash not valid.") if len(proof.secret.split("P2SH:")) == 2:
print(f"Script {proof.script} valid.") # secret indicates a script but no script is present
return True return False
else:
# secret indicates no script, so treat script as valid
return True
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}"
return valid
def _verify_outputs( def _verify_outputs(
self, total: int, amount: int, output_data: List[BlindedMessage] self, total: int, amount: int, output_data: List[BlindedMessage]
@@ -255,6 +270,11 @@ class Ledger:
"""Consumes proofs and prepares new promises based on the amount split.""" """Consumes proofs and prepares new promises based on the amount split."""
total = sum([p.amount for p in proofs]) total = sum([p.amount for p in proofs])
# if not all([self._verify_secret_or_script(p) for p in proofs]):
# raise Exception("can't use secret and script at the same time.")
# Verify scripts
if not all([self._verify_script(i, p) for i, p in enumerate(proofs)]):
raise Exception("could not verify scripts.")
# verify that amount is kosher # verify that amount is kosher
self._verify_split_amount(amount) self._verify_split_amount(amount)
# verify overspending attempt # verify overspending attempt
@@ -272,9 +292,6 @@ class Ledger:
# Verify proofs # Verify proofs
if not all([self._verify_proof(p) for p in proofs]): if not all([self._verify_proof(p) for p in proofs]):
raise Exception("could not verify proofs.") raise Exception("could not verify proofs.")
# Verify scripts
if not all([self._verify_script(p) for p in proofs]):
raise Exception("could not verify scripts.")
# Mark proofs as used and prepare new promises # Mark proofs as used and prepare new promises
await self._invalidate_proofs(proofs) await self._invalidate_proofs(proofs)

View File

@@ -3,7 +3,8 @@ from typing import Union
from fastapi import APIRouter from fastapi import APIRouter
from secp256k1 import PublicKey from secp256k1 import PublicKey
from cashu.core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload from cashu.core.base import (CheckPayload, MeltPayload, MintPayloads,
SplitPayload)
from cashu.mint import ledger from cashu.mint import ledger
router: APIRouter = APIRouter() router: APIRouter = APIRouter()

View File

@@ -5,7 +5,6 @@ import base64
import json import json
import math import math
import sys import sys
from loguru import logger
from datetime import datetime from datetime import datetime
from functools import wraps from functools import wraps
from itertools import groupby from itertools import groupby
@@ -13,12 +12,14 @@ from operator import itemgetter
import click import click
from bech32 import bech32_decode, bech32_encode, convertbits from bech32 import bech32_decode, bech32_encode, convertbits
from loguru import logger
import cashu.core.bolt11 as bolt11 import cashu.core.bolt11 as bolt11
from cashu.core.base import Proof from cashu.core.base import Proof
from cashu.core.bolt11 import Invoice from cashu.core.bolt11 import Invoice
from cashu.core.helpers import fee_reserve from cashu.core.helpers import fee_reserve
from cashu.core.migrations import migrate_databases from cashu.core.migrations import migrate_databases
from cashu.core.script import *
from cashu.core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL, VERSION from cashu.core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL, VERSION
from cashu.wallet import migrations from cashu.wallet import migrations
from cashu.wallet.crud import get_reserved_proofs from cashu.wallet.crud import get_reserved_proofs
@@ -123,19 +124,43 @@ async def send(ctx, amount: int, secret: str):
@cli.command("receive", help="Receive tokens.") @cli.command("receive", help="Receive tokens.")
@click.argument("token", type=str) @click.argument("token", type=str)
@click.option("--secret", "-s", default="", help="Token spending condition.", type=str) @click.option("--secret", "-s", default=None, help="Token secret.", type=str)
@click.option("--script", default=None, help="Token unlock script.", type=str) @click.option("--script", default=None, help="Unlock script.", type=str)
@click.option("--signature", default=None, help="Script signature.", type=str)
@click.pass_context @click.pass_context
@coro @coro
async def receive(ctx, token: str, secret: str, script: str): async def receive(ctx, token: str, secret: str, script: str, signature: str):
wallet: Wallet = ctx.obj["WALLET"] wallet: Wallet = ctx.obj["WALLET"]
wallet.load_mint() wallet.load_mint()
wallet.status() wallet.status()
proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))] proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))]
_, _ = await wallet.redeem(proofs, secret, script) _, _ = await wallet.redeem(
proofs, snd_secret=secret, snd_script=script, snd_siganture=signature
)
wallet.status() wallet.status()
@cli.command("address", help="Generate receiving address.")
@click.pass_context
@coro
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("")
print(f"Send via command:\ncashu send <amount> --secret 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(
f"Receive via command:\ncashu receive <token> --secret P2SH:{txin_p2sh_address} --script {txin_redeemScript_b64} --signature {txin_signature_b64}"
)
@cli.command("burn", help="Burn spent tokens.") @cli.command("burn", help="Burn spent tokens.")
@click.argument("token", required=False, type=str) @click.argument("token", required=False, type=str)
@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent tokens.") @click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent tokens.")

View File

@@ -3,31 +3,20 @@ import json
import secrets as scrts import secrets as scrts
import uuid import uuid
from typing import List from typing import List
from loguru import logger
import requests import requests
from loguru import logger
import cashu.core.b_dhke as b_dhke import cashu.core.b_dhke as b_dhke
from cashu.core.base import ( from cashu.core.base import (BlindedMessage, BlindedSignature, CheckPayload,
BlindedMessage, MeltPayload, MintPayloads, P2SHScript, Proof,
BlindedSignature, SplitPayload)
CheckPayload,
MeltPayload,
MintPayloads,
Proof,
SplitPayload,
)
from cashu.core.db import Database from cashu.core.db import Database
from cashu.core.secp import PublicKey from cashu.core.secp import PublicKey
from cashu.core.settings import DEBUG from cashu.core.settings import DEBUG
from cashu.core.split import amount_split from cashu.core.split import amount_split
from cashu.wallet.crud import ( from cashu.wallet.crud import (get_proofs, invalidate_proof, secret_used,
get_proofs, store_proof, update_proof_reserved)
invalidate_proof,
store_proof,
update_proof_reserved,
secret_used,
)
class LedgerAPI: class LedgerAPI:
@@ -132,9 +121,7 @@ class LedgerAPI:
promises = [BlindedSignature.from_dict(p) for p in promises_list] promises = [BlindedSignature.from_dict(p) for p in promises_list]
return self._construct_proofs(promises, secrets, rs) return self._construct_proofs(promises, secrets, rs)
async def split( async def split(self, proofs, amount, snd_secret: str = None):
self, proofs, amount, snd_secret: str = None, has_script: bool = False
):
"""Consume proofs and create new promises based on amount split. """Consume proofs and create new promises based on amount split.
If snd_secret is None, random secrets will be generated for the tokens to keep (fst_outputs) If snd_secret is None, random secrets will be generated for the tokens to keep (fst_outputs)
and the promises to send (snd_outputs). and the promises to send (snd_outputs).
@@ -182,7 +169,7 @@ class LedgerAPI:
else: else:
raise Exception("Unkown mint error.") raise Exception("Unkown mint error.")
if "error" in promises_dict: if "error" in promises_dict:
raise Exception("Error: {}".format(promises_dict["error"])) raise Exception("Mint Error: {}".format(promises_dict["error"]))
promises_fst = [BlindedSignature.from_dict(p) for p in promises_dict["fst"]] promises_fst = [BlindedSignature.from_dict(p) for p in promises_dict["fst"]]
promises_snd = [BlindedSignature.from_dict(p) for p in promises_dict["snd"]] promises_snd = [BlindedSignature.from_dict(p) for p in promises_dict["snd"]]
# Construct proofs from promises (i.e., unblind signatures) # Construct proofs from promises (i.e., unblind signatures)
@@ -245,7 +232,11 @@ class Wallet(LedgerAPI):
return proofs return proofs
async def redeem( async def redeem(
self, proofs: List[Proof], snd_secret: str = None, snd_script: str = None self,
proofs: List[Proof],
snd_secret: str = None,
snd_script: str = None,
snd_siganture: str = None,
): ):
if snd_secret: if snd_secret:
logger.debug(f"Redeption secret: {snd_secret}") logger.debug(f"Redeption secret: {snd_secret}")
@@ -254,13 +245,15 @@ class Wallet(LedgerAPI):
# overload proofs with custom secrets for redemption # overload proofs with custom secrets for redemption
for p, s in zip(proofs, snd_secrets): for p, s in zip(proofs, snd_secrets):
p.secret = s p.secret = s
if snd_script: has_script = False
if snd_script and snd_siganture:
has_script = True
logger.debug(f"Unlock script: {snd_script}") logger.debug(f"Unlock script: {snd_script}")
# overload proofs with unlock script # overload proofs with unlock script
for p in proofs: for p in proofs:
p.script = snd_script p.script = P2SHScript(script=snd_script, signature=snd_siganture)
return await self.split( return await self.split(
proofs, sum(p["amount"] for p in proofs), has_script=snd_script is not None proofs, sum(p["amount"] for p in proofs), has_script=has_script
) )
async def split( async def split(
@@ -271,9 +264,7 @@ class Wallet(LedgerAPI):
has_script: bool = False, has_script: bool = False,
): ):
assert len(proofs) > 0, ValueError("no proofs provided.") assert len(proofs) > 0, ValueError("no proofs provided.")
fst_proofs, snd_proofs = await super().split( fst_proofs, snd_proofs = await super().split(proofs, amount, snd_secret)
proofs, amount, snd_secret, has_script
)
if len(fst_proofs) == 0 and len(snd_proofs) == 0: if len(fst_proofs) == 0 and len(snd_proofs) == 0:
raise Exception("received no splits.") raise Exception("received no splits.")
used_secrets = [p["secret"] for p in proofs] used_secrets = [p["secret"] for p in proofs]

View File

@@ -1,5 +1,6 @@
import time import time
from re import S from re import S
from cashu.core.helpers import async_unwrap from cashu.core.helpers import async_unwrap
from cashu.core.migrations import migrate_databases from cashu.core.migrations import migrate_databases
from cashu.wallet import migrations from cashu.wallet import migrations