mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
* fix keys * fix tests * backwards compatible api upgrade * upgrade seems to work * fix tests * add deprecated api functions * add more tests of backwards compat * add test serialization for nut00 * remove a redundant test * move mint and melt to new api * mypy works * CI: mypy --check-untyped-defs * add deprecated router * add hints and remove logs * fix tests * cleanup * use new mint and melt endpoints * tests passing? * fix mypy * make format * make format * make format * commit * errors gone * save * adjust the API * store quotes in db * make mypy happy * add fakewallet settings * remove LIGHTNING=True and pass quote id for melt * format * tests passing * add CoreLightningRestWallet * add macaroon loader * add correct config * preimage -> proof * move wallet.status() to cli.helpers.print_status() * remove statuses from tests * remove * make format * Use httpx in deprecated wallet * fix cln interface * create invoice before quote * internal transactions and deprecated api testing * fix tests * add deprecated API tests * fastapi type hints break things * fix duplicate wallet error * make format * update poetry in CI to 1.7.1 * precommit restore * remove bolt11 * oops * default poetry * store fee reserve for melt quotes and refactor melt() * works? * make format * test * finally * fix deprecated models * rename v1 endpoints to bolt11 * raise restore and check to v1, bump version to 0.15.0 * add version byte to keyset id * remove redundant fields in json * checks * generate bip32 keyset wip * migrate old keysets * load duplicate keys * duplicate old keysets * revert router changes * add deprecated /check and /restore endpoints * try except invalidate * parse unit from derivation path, adjust keyset id calculation with bytes * remove keyest id from functions again and rely on self.keyset_id * mosts tests work * mint loads multiple derivation paths * make format * properly print units * fix tests * wallet works with multiple units * add strike wallet and choose backend dynamically * fix mypy * add get_payment_quote to lightning backends * make format * fix startup * fix lnbitswallet * fix tests * LightningWallet -> LightningBackend * remove comments * make format * remove msat conversion * add Amount type * fix regtest * use melt_quote as argument for pay_invoice * test old api * fees in sats * fix deprecated fees * fixes * print balance correctly * internally index keyset response by int * add pydantic validation to input models * add timestamps to mint db * store timestamps for invoices, promises, proofs_used * fix wallet migration * rotate keys correctly for testing * remove print * update latest keyset * fix tests * fix test * make format * make format with correct black version * remove nsat and cheese * test against deprecated mint * fix tests? * actually use env var * mint run with env vars * moar test * cleanup * simplify tests, load all keys * try out testing with internal invoices * fix internal melt test * fix test * deprecated checkfees expects appropriate fees * adjust comment * drop lightning table * split migration for testing for now, remove it later * remove unused lightning table * skip_private_key -> skip_db_read * throw error on migration error * reorder * fix migrations * fix lnbits fee return value negative * fix typo * comments * add type * make format * split must use correct amount * fix tests * test deprecated api with internal/external melts * do not split if not necessary * refactor * fix test * make format with new black * cleanup and add comments * add quote state check endpoints * fix deprecated wallet response * split -> swap endpoint * make format * add expiry to quotes, get quote endpoints, and adjust to nut review comments * allow overpayment of melt * add lightning wallet tests * commiting to save * fix tests a bit * make format * remove comments * get mint info * check_spendable default False, and return payment quote checking id * make format * bump version in pyproject * update to /v1/checkstate * make format * fix mint api checks * return witness on /v1/checkstate * no failfast * try fail-fast: false in ci.yaml * fix db lookup * clean up literals
191 lines
6.8 KiB
Python
191 lines
6.8 KiB
Python
from datetime import datetime, timedelta
|
|
from typing import List, Optional
|
|
|
|
from loguru import logger
|
|
|
|
from ..core.base import (
|
|
BlindedMessage,
|
|
P2PKWitness,
|
|
Proof,
|
|
)
|
|
from ..core.crypto.secp import PrivateKey
|
|
from ..core.db import Database
|
|
from ..core.p2pk import (
|
|
P2PKSecret,
|
|
SigFlags,
|
|
sign_p2pk_sign,
|
|
)
|
|
from ..core.secret import Secret, SecretKind, Tags
|
|
from .protocols import SupportsDb, SupportsPrivateKey
|
|
|
|
|
|
class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|
db: Database
|
|
private_key: Optional[PrivateKey] = None
|
|
# ---------- P2PK ----------
|
|
|
|
async def create_p2pk_pubkey(self):
|
|
assert (
|
|
self.private_key
|
|
), "No private key set in settings. Set NOSTR_PRIVATE_KEY in .env"
|
|
public_key = self.private_key.pubkey
|
|
# logger.debug(f"Private key: {self.private_key.bech32()}")
|
|
assert public_key
|
|
return public_key.serialize().hex()
|
|
|
|
async def create_p2pk_lock(
|
|
self,
|
|
pubkey: str,
|
|
locktime_seconds: Optional[int] = None,
|
|
tags: Optional[Tags] = None,
|
|
sig_all: bool = False,
|
|
n_sigs: int = 1,
|
|
) -> P2PKSecret:
|
|
logger.debug(f"Provided tags: {tags}")
|
|
if not tags:
|
|
tags = Tags()
|
|
logger.debug(f"Before tags: {tags}")
|
|
if locktime_seconds:
|
|
tags["locktime"] = str(
|
|
int((datetime.now() + timedelta(seconds=locktime_seconds)).timestamp())
|
|
)
|
|
tags["sigflag"] = (
|
|
SigFlags.SIG_ALL.value if sig_all else SigFlags.SIG_INPUTS.value
|
|
)
|
|
if n_sigs > 1:
|
|
tags["n_sigs"] = str(n_sigs)
|
|
logger.debug(f"After tags: {tags}")
|
|
return P2PKSecret(
|
|
kind=SecretKind.P2PK.value,
|
|
data=pubkey,
|
|
tags=tags,
|
|
)
|
|
|
|
async def sign_p2pk_proofs(self, proofs: List[Proof]) -> List[str]:
|
|
assert (
|
|
self.private_key
|
|
), "No private key set in settings. Set NOSTR_PRIVATE_KEY in .env"
|
|
private_key = self.private_key
|
|
assert private_key.pubkey
|
|
logger.trace(
|
|
f"Signing with private key: {private_key.serialize()} public key:"
|
|
f" {private_key.pubkey.serialize().hex()}"
|
|
)
|
|
for proof in proofs:
|
|
logger.trace(f"Signing proof: {proof}")
|
|
logger.trace(f"Signing message: {proof.secret}")
|
|
|
|
signatures = [
|
|
sign_p2pk_sign(
|
|
message=proof.secret.encode("utf-8"),
|
|
private_key=private_key,
|
|
)
|
|
for proof in proofs
|
|
]
|
|
logger.debug(f"Signatures: {signatures}")
|
|
return signatures
|
|
|
|
async def sign_p2pk_outputs(self, outputs: List[BlindedMessage]) -> List[str]:
|
|
assert (
|
|
self.private_key
|
|
), "No private key set in settings. Set NOSTR_PRIVATE_KEY in .env"
|
|
private_key = self.private_key
|
|
assert private_key.pubkey
|
|
return [
|
|
sign_p2pk_sign(
|
|
message=output.B_.encode("utf-8"),
|
|
private_key=private_key,
|
|
)
|
|
for output in outputs
|
|
]
|
|
|
|
async def add_p2pk_witnesses_to_outputs(
|
|
self, outputs: List[BlindedMessage]
|
|
) -> List[BlindedMessage]:
|
|
"""Takes a list of outputs and adds a P2PK signatures to each.
|
|
Args:
|
|
outputs (List[BlindedMessage]): Outputs to add P2PK signatures to
|
|
Returns:
|
|
List[BlindedMessage]: Outputs with P2PK signatures added
|
|
"""
|
|
p2pk_signatures = await self.sign_p2pk_outputs(outputs)
|
|
for o, s in zip(outputs, p2pk_signatures):
|
|
o.witness = P2PKWitness(signatures=[s]).json()
|
|
return outputs
|
|
|
|
async def add_witnesses_to_outputs(
|
|
self, proofs: List[Proof], outputs: List[BlindedMessage]
|
|
) -> List[BlindedMessage]:
|
|
"""Adds witnesses to outputs if the inputs (proofs) indicate an appropriate signature flag
|
|
|
|
Args:
|
|
proofs (List[Proof]): Inputs to the transaction
|
|
outputs (List[BlindedMessage]): Outputs to add witnesses to
|
|
Returns:
|
|
List[BlindedMessage]: Outputs with signatures added
|
|
"""
|
|
# first we check whether all tokens have serialized secrets as their secret
|
|
try:
|
|
for p in proofs:
|
|
Secret.deserialize(p.secret)
|
|
except Exception:
|
|
# if not, we do not add witnesses (treat as regular token secret)
|
|
return outputs
|
|
|
|
# if any of the proofs provided require SIG_ALL, we must provide it
|
|
if any([
|
|
P2PKSecret.deserialize(p.secret).sigflag == SigFlags.SIG_ALL for p in proofs
|
|
]):
|
|
outputs = await self.add_p2pk_witnesses_to_outputs(outputs)
|
|
return outputs
|
|
|
|
async def add_p2pk_witnesses_to_proofs(self, proofs: List[Proof]) -> List[Proof]:
|
|
p2pk_signatures = await self.sign_p2pk_proofs(proofs)
|
|
logger.debug(f"Unlock signatures for {len(proofs)} proofs: {p2pk_signatures}")
|
|
logger.debug(f"Proofs: {proofs}")
|
|
# attach unlock signatures to proofs
|
|
assert len(proofs) == len(p2pk_signatures), "wrong number of signatures"
|
|
for p, s in zip(proofs, p2pk_signatures):
|
|
# if there are already signatures, append
|
|
if p.witness and P2PKWitness.from_witness(p.witness).signatures:
|
|
signatures = P2PKWitness.from_witness(p.witness).signatures
|
|
p.witness = P2PKWitness(signatures=signatures + [s]).json()
|
|
else:
|
|
p.witness = P2PKWitness(signatures=[s]).json()
|
|
return proofs
|
|
|
|
async def add_witnesses_to_proofs(self, proofs: List[Proof]) -> List[Proof]:
|
|
"""Adds witnesses to proofs for P2PK redemption.
|
|
|
|
This method parses the secret of each proof and determines the correct
|
|
witness type and adds it to the proof if we have it available.
|
|
|
|
Note: In order for this method to work, all proofs must have the same secret type.
|
|
For P2PK, we use an individual signature for each token in proofs.
|
|
|
|
Args:
|
|
proofs (List[Proof]): List of proofs to add witnesses to
|
|
|
|
Returns:
|
|
List[Proof]: List of proofs with witnesses added
|
|
"""
|
|
|
|
# iterate through proofs and produce witnesses for each
|
|
|
|
# first we check whether all tokens have serialized secrets as their secret
|
|
try:
|
|
for p in proofs:
|
|
Secret.deserialize(p.secret)
|
|
except Exception:
|
|
# if not, we do not add witnesses (treat as regular token secret)
|
|
return proofs
|
|
logger.debug("Spending conditions detected.")
|
|
# P2PK signatures
|
|
if all([
|
|
Secret.deserialize(p.secret).kind == SecretKind.P2PK.value for p in proofs
|
|
]):
|
|
logger.debug("P2PK redemption detected.")
|
|
proofs = await self.add_p2pk_witnesses_to_proofs(proofs)
|
|
|
|
return proofs
|