[Wallet/mint] P2PK with timelocks (#270)

* p2pk with nostr privatekey and timelocks

* add p2pk

* fix test

* fix test with custom secret

* sign whole split transaction

* p2pk signature now commits to entire secret and thus to a nonce

* use schnorr signatures

* revamp P2SH and P2PK with new Secret model

* test p2pk

* add comments

* add nostr private key to tests

* fix nostr receive

* make format

* test redemption after timelock

* refactor Server.serialize()

* sign sha256(secret)

* add optional refund pubkey that triggers after timelock

* use nostr private key for now (including nsec parser)

* use nostr private key and fix tests

* bump version to 0.12.2
This commit is contained in:
callebtc
2023-07-02 01:56:05 +02:00
committed by GitHub
parent 4beaf8ff41
commit 01d498309b
15 changed files with 548 additions and 231 deletions

View File

@@ -1,11 +1,13 @@
import base64
import json
import os
from datetime import datetime, timedelta
from typing import List, Optional
import click
from loguru import logger
from ..core.base import TokenV1, TokenV2, TokenV3, TokenV3Token
from ..core.base import Secret, SecretKind, TokenV1, TokenV2, TokenV3, TokenV3Token
from ..core.helpers import sum_proofs
from ..core.migrations import migrate_databases
from ..core.settings import settings
@@ -21,12 +23,7 @@ async def init_wallet(wallet: Wallet, load_proofs: bool = True):
await wallet.load_proofs(reload=True)
async def redeem_TokenV3_multimint(
wallet: Wallet,
token: TokenV3,
script,
signature,
):
async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3):
"""
Helper function to iterate thruogh a token with multiple mints and redeem them from
these mints one keyset at a time.
@@ -43,9 +40,7 @@ async def redeem_TokenV3_multimint(
await mint_wallet.load_mint()
# redeem proofs of this keyset
redeem_proofs = [p for p in t.proofs if p.id == keyset]
_, _ = await mint_wallet.redeem(
redeem_proofs, scnd_script=script, scnd_siganture=signature
)
_, _ = await mint_wallet.redeem(redeem_proofs)
print(f"Received {sum_proofs(redeem_proofs)} sats")
@@ -113,20 +108,9 @@ def deserialize_token_from_string(token: str) -> TokenV3:
async def receive(
wallet: Wallet,
tokenObj: TokenV3,
lock: str,
):
# check for P2SH locks
if lock:
# load the script and signature of this address from the database
assert len(lock.split("P2SH:")) == 2, Exception(
"lock has wrong format. Expected P2SH:<address>."
)
address_split = lock.split("P2SH:")[1]
p2shscripts = await get_unused_locks(address_split, db=wallet.db)
assert len(p2shscripts) == 1, Exception("lock not found.")
script, signature = p2shscripts[0].script, p2shscripts[0].signature
else:
script, signature = None, None
logger.debug(f"receive: {tokenObj}")
proofs = [p for t in tokenObj.token for p in t.proofs]
includes_mint_info: bool = any([t.mint for t in tokenObj.token])
@@ -135,13 +119,10 @@ async def receive(
await redeem_TokenV3_multimint(
wallet,
tokenObj,
script,
signature,
)
else:
# this is very legacy code, virtually any token should have mint information
# no mint information present, we extract the proofs and use wallet's default mint
proofs = [p for t in tokenObj.token for p in t.proofs]
# first we load the mint URL from the DB
keyset_in_token = proofs[0].id
assert keyset_in_token
@@ -155,7 +136,7 @@ async def receive(
os.path.join(settings.cashu_dir, wallet.name),
)
await mint_wallet.load_mint(keyset_in_token)
_, _ = await mint_wallet.redeem(proofs, script, signature)
_, _ = await mint_wallet.redeem(proofs)
print(f"Received {sum_proofs(proofs)} sats")
# reload main wallet so the balance updates
@@ -170,19 +151,34 @@ async def send(
"""
Prints token to send to stdout.
"""
secret_lock = None
if lock:
assert len(lock) > 21, Exception(
"Error: lock has to be at least 22 characters long."
)
p2sh = False
if lock and len(lock.split("P2SH:")) == 2:
p2sh = True
if not lock.startswith("P2SH:") and not lock.startswith("P2PK:"):
raise Exception("Error: lock has to start with P2SH: or P2PK:")
# we add a time lock to the P2PK lock by appending the current unix time + 14 days
# we use datetime because it's easier to read
if lock.startswith("P2PK:") or lock.startswith("P2SH:"):
logger.debug(f"Locking token to: {lock}")
logger.debug(
f"Adding a time lock of {settings.timelock_delta_seconds} seconds."
)
if lock.startswith("P2SH:"):
secret_lock = await wallet.create_p2sh_lock(
lock.split(":")[1], timelock=settings.timelock_delta_seconds
)
elif lock.startswith("P2PK:"):
secret_lock = await wallet.create_p2pk_lock(
lock.split(":")[1], timelock=settings.timelock_delta_seconds
)
await wallet.load_proofs()
if split:
await wallet.load_mint()
_, send_proofs = await wallet.split_to_send(
wallet.proofs, amount, lock, set_reserved=True
wallet.proofs, amount, secret_lock, set_reserved=True
)
else:
# get a proof with specific amount