mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-22 19:34:18 +01:00
Refactor conditions and fix HTLC multisig (#643)
* refactor conditions and fix htlc multisig * restore db/write.py * safer check for P2PK secrets for SIG_ALL * comment cleanup
This commit is contained in:
@@ -95,21 +95,7 @@ class ProofState(LedgerEvent):
|
|||||||
|
|
||||||
class HTLCWitness(BaseModel):
|
class HTLCWitness(BaseModel):
|
||||||
preimage: Optional[str] = None
|
preimage: Optional[str] = None
|
||||||
signature: Optional[str] = None
|
signatures: Optional[List[str]] = None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_witness(cls, witness: str):
|
|
||||||
return cls(**json.loads(witness))
|
|
||||||
|
|
||||||
|
|
||||||
class P2SHWitness(BaseModel):
|
|
||||||
"""
|
|
||||||
Unlocks P2SH spending condition of a Proof
|
|
||||||
"""
|
|
||||||
|
|
||||||
script: str
|
|
||||||
signature: str
|
|
||||||
address: Union[str, None] = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_witness(cls, witness: str):
|
def from_witness(cls, witness: str):
|
||||||
@@ -206,10 +192,15 @@ class Proof(BaseModel):
|
|||||||
return P2PKWitness.from_witness(self.witness).signatures
|
return P2PKWitness.from_witness(self.witness).signatures
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def htlcpreimage(self) -> Union[str, None]:
|
def htlcpreimage(self) -> str | None:
|
||||||
assert self.witness, "Witness is missing for htlc preimage"
|
assert self.witness, "Witness is missing for htlc preimage"
|
||||||
return HTLCWitness.from_witness(self.witness).preimage
|
return HTLCWitness.from_witness(self.witness).preimage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def htlcsigs(self) -> List[str] | None:
|
||||||
|
assert self.witness, "Witness is missing for htlc signatures"
|
||||||
|
return HTLCWitness.from_witness(self.witness).signatures
|
||||||
|
|
||||||
|
|
||||||
class Proofs(BaseModel):
|
class Proofs(BaseModel):
|
||||||
# NOTE: not used in Pydantic validation
|
# NOTE: not used in Pydantic validation
|
||||||
@@ -647,6 +638,7 @@ class WalletKeyset:
|
|||||||
int(amount): PublicKey(bytes.fromhex(hex_key), raw=True)
|
int(amount): PublicKey(bytes.fromhex(hex_key), raw=True)
|
||||||
for amount, hex_key in dict(json.loads(serialized)).items()
|
for amount, hex_key in dict(json.loads(serialized)).items()
|
||||||
}
|
}
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
id=row["id"],
|
id=row["id"],
|
||||||
unit=row["unit"],
|
unit=row["unit"],
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
|
from enum import Enum
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from .secret import Secret, SecretKind
|
from .secret import Secret, SecretKind
|
||||||
|
|
||||||
|
|
||||||
|
class SigFlags(Enum):
|
||||||
|
# require signatures only on the inputs (default signature flag)
|
||||||
|
SIG_INPUTS = "SIG_INPUTS"
|
||||||
|
# require signatures on inputs and outputs
|
||||||
|
SIG_ALL = "SIG_ALL"
|
||||||
|
|
||||||
|
|
||||||
class HTLCSecret(Secret):
|
class HTLCSecret(Secret):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_secret(cls, secret: Secret):
|
def from_secret(cls, secret: Secret):
|
||||||
@@ -15,3 +23,13 @@ class HTLCSecret(Secret):
|
|||||||
def locktime(self) -> Union[None, int]:
|
def locktime(self) -> Union[None, int]:
|
||||||
locktime = self.tags.get_tag("locktime")
|
locktime = self.tags.get_tag("locktime")
|
||||||
return int(locktime) if locktime else None
|
return int(locktime) if locktime else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sigflag(self) -> Union[None, SigFlags]:
|
||||||
|
sigflag = self.tags.get_tag("sigflag")
|
||||||
|
return SigFlags(sigflag) if sigflag else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_sigs(self) -> Union[None, int]:
|
||||||
|
n_sigs = self.tags.get_tag("n_sigs")
|
||||||
|
return int(n_sigs) if n_sigs else None
|
||||||
|
|||||||
@@ -68,18 +68,16 @@ class P2PKSecret(Secret):
|
|||||||
return int(n_sigs) if n_sigs else None
|
return int(n_sigs) if n_sigs else None
|
||||||
|
|
||||||
|
|
||||||
def sign_p2pk_sign(message: bytes, private_key: PrivateKey) -> bytes:
|
def schnorr_sign(message: bytes, private_key: PrivateKey) -> bytes:
|
||||||
# ecdsa version
|
|
||||||
# signature = private_key.ecdsa_serialize(private_key.ecdsa_sign(message))
|
|
||||||
signature = private_key.schnorr_sign(
|
signature = private_key.schnorr_sign(
|
||||||
hashlib.sha256(message).digest(), None, raw=True
|
hashlib.sha256(message).digest(), None, raw=True
|
||||||
)
|
)
|
||||||
return signature
|
return signature
|
||||||
|
|
||||||
|
|
||||||
def verify_p2pk_signature(message: bytes, pubkey: PublicKey, signature: bytes) -> bool:
|
def verify_schnorr_signature(
|
||||||
# ecdsa version
|
message: bytes, pubkey: PublicKey, signature: bytes
|
||||||
# return pubkey.ecdsa_verify(message, pubkey.ecdsa_deserialize(signature))
|
) -> bool:
|
||||||
return pubkey.schnorr_verify(
|
return pubkey.schnorr_verify(
|
||||||
hashlib.sha256(message).digest(), signature, None, raw=True
|
hashlib.sha256(message).digest(), signature, None, raw=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import List
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from ..core.base import BlindedMessage, HTLCWitness, Proof
|
from ..core.base import BlindedMessage, Proof
|
||||||
from ..core.crypto.secp import PublicKey
|
from ..core.crypto.secp import PublicKey
|
||||||
from ..core.errors import (
|
from ..core.errors import (
|
||||||
TransactionError,
|
TransactionError,
|
||||||
@@ -13,7 +13,7 @@ from ..core.htlc import HTLCSecret
|
|||||||
from ..core.p2pk import (
|
from ..core.p2pk import (
|
||||||
P2PKSecret,
|
P2PKSecret,
|
||||||
SigFlags,
|
SigFlags,
|
||||||
verify_p2pk_signature,
|
verify_schnorr_signature,
|
||||||
)
|
)
|
||||||
from ..core.secret import Secret, SecretKind
|
from ..core.secret import Secret, SecretKind
|
||||||
|
|
||||||
@@ -50,63 +50,10 @@ class LedgerSpendingConditions:
|
|||||||
if not pubkeys:
|
if not pubkeys:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
assert len(set(pubkeys)) == len(pubkeys), "pubkeys must be unique."
|
return self._verify_secret_signatures(
|
||||||
logger.trace(f"pubkeys: {pubkeys}")
|
proof, pubkeys, proof.p2pksigs, p2pk_secret.n_sigs
|
||||||
|
|
||||||
# verify that signatures are present
|
|
||||||
if not proof.p2pksigs:
|
|
||||||
# no signature present although secret indicates one
|
|
||||||
logger.error(f"no p2pk signatures in proof: {proof.p2pksigs}")
|
|
||||||
raise TransactionError("no p2pk signatures in proof.")
|
|
||||||
|
|
||||||
# we make sure that there are no duplicate signatures
|
|
||||||
if len(set(proof.p2pksigs)) != len(proof.p2pksigs):
|
|
||||||
raise TransactionError("p2pk signatures must be unique.")
|
|
||||||
|
|
||||||
# we parse the secret as a P2PK commitment
|
|
||||||
# assert len(proof.secret.split(":")) == 5, "p2pk secret format invalid."
|
|
||||||
|
|
||||||
# INPUTS: check signatures proof.p2pksigs against pubkey
|
|
||||||
# we expect the signature to be on the pubkey (=message) itself
|
|
||||||
n_sigs_required = p2pk_secret.n_sigs or 1
|
|
||||||
assert n_sigs_required > 0, "n_sigs must be positive."
|
|
||||||
|
|
||||||
# check if enough signatures are present
|
|
||||||
assert (
|
|
||||||
len(proof.p2pksigs) >= n_sigs_required
|
|
||||||
), f"not enough signatures provided: {len(proof.p2pksigs)} < {n_sigs_required}."
|
|
||||||
|
|
||||||
n_valid_sigs_per_output = 0
|
|
||||||
# loop over all signatures in output
|
|
||||||
for input_sig in proof.p2pksigs:
|
|
||||||
for pubkey in pubkeys:
|
|
||||||
logger.trace(f"verifying signature {input_sig} by pubkey {pubkey}.")
|
|
||||||
logger.trace(f"Message: {p2pk_secret.serialize().encode('utf-8')}")
|
|
||||||
if verify_p2pk_signature(
|
|
||||||
message=proof.secret.encode("utf-8"),
|
|
||||||
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
|
|
||||||
signature=bytes.fromhex(input_sig),
|
|
||||||
):
|
|
||||||
n_valid_sigs_per_output += 1
|
|
||||||
logger.trace(
|
|
||||||
f"p2pk signature on input is valid: {input_sig} on {pubkey}."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# check if we have enough valid signatures
|
|
||||||
assert n_valid_sigs_per_output, "no valid signature provided for input."
|
|
||||||
assert n_valid_sigs_per_output >= n_sigs_required, (
|
|
||||||
f"signature threshold not met. {n_valid_sigs_per_output} <"
|
|
||||||
f" {n_sigs_required}."
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.trace(
|
|
||||||
f"{n_valid_sigs_per_output} of {n_sigs_required} valid signatures found."
|
|
||||||
)
|
|
||||||
logger.trace(proof.p2pksigs)
|
|
||||||
logger.trace("p2pk signature on inputs is valid.")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _verify_htlc_spending_conditions(self, proof: Proof, secret: Secret) -> bool:
|
def _verify_htlc_spending_conditions(self, proof: Proof, secret: Secret) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify HTLC spending condition for a single input.
|
Verify HTLC spending condition for a single input.
|
||||||
@@ -149,18 +96,9 @@ class LedgerSpendingConditions:
|
|||||||
if htlc_secret.locktime and htlc_secret.locktime < time.time():
|
if htlc_secret.locktime and htlc_secret.locktime < time.time():
|
||||||
refund_pubkeys = htlc_secret.tags.get_tag_all("refund")
|
refund_pubkeys = htlc_secret.tags.get_tag_all("refund")
|
||||||
if refund_pubkeys:
|
if refund_pubkeys:
|
||||||
assert proof.witness, TransactionError("no HTLC refund signature.")
|
return self._verify_secret_signatures(
|
||||||
signature = HTLCWitness.from_witness(proof.witness).signature
|
proof, refund_pubkeys, proof.p2pksigs, htlc_secret.n_sigs
|
||||||
assert signature, TransactionError("no HTLC refund signature provided")
|
)
|
||||||
for pubkey in refund_pubkeys:
|
|
||||||
if verify_p2pk_signature(
|
|
||||||
message=proof.secret.encode("utf-8"),
|
|
||||||
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
|
|
||||||
signature=bytes.fromhex(signature),
|
|
||||||
):
|
|
||||||
# a signature matches
|
|
||||||
return True
|
|
||||||
raise TransactionError("HTLC refund signatures did not match.")
|
|
||||||
# no pubkeys given in secret, anyone can spend
|
# no pubkeys given in secret, anyone can spend
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -173,23 +111,74 @@ class LedgerSpendingConditions:
|
|||||||
).digest() == bytes.fromhex(htlc_secret.data):
|
).digest() == bytes.fromhex(htlc_secret.data):
|
||||||
raise TransactionError("HTLC preimage does not match.")
|
raise TransactionError("HTLC preimage does not match.")
|
||||||
|
|
||||||
# then we check whether a signature is required
|
# then we check whether signatures are required
|
||||||
hashlock_pubkeys = htlc_secret.tags.get_tag_all("pubkeys")
|
hashlock_pubkeys = htlc_secret.tags.get_tag_all("pubkeys")
|
||||||
if hashlock_pubkeys:
|
if not hashlock_pubkeys:
|
||||||
assert proof.witness, TransactionError("no HTLC hash lock signature.")
|
# no pubkeys given in secret, anyone can spend
|
||||||
signature = HTLCWitness.from_witness(proof.witness).signature
|
return True
|
||||||
assert signature, TransactionError("HTLC no hash lock signatures provided.")
|
|
||||||
for pubkey in hashlock_pubkeys:
|
return self._verify_secret_signatures(
|
||||||
if verify_p2pk_signature(
|
proof, hashlock_pubkeys, proof.htlcsigs or [], htlc_secret.n_sigs
|
||||||
|
)
|
||||||
|
|
||||||
|
def _verify_secret_signatures(
|
||||||
|
self,
|
||||||
|
proof: Proof,
|
||||||
|
pubkeys: List[str],
|
||||||
|
signatures: List[str],
|
||||||
|
n_sigs_required: int | None = 1,
|
||||||
|
) -> bool:
|
||||||
|
assert len(set(pubkeys)) == len(pubkeys), "pubkeys must be unique."
|
||||||
|
logger.trace(f"pubkeys: {pubkeys}")
|
||||||
|
|
||||||
|
# verify that signatures are present
|
||||||
|
if not signatures:
|
||||||
|
# no signature present although secret indicates one
|
||||||
|
logger.error(f"no signatures in proof: {proof}")
|
||||||
|
raise TransactionError("no signatures in proof.")
|
||||||
|
|
||||||
|
# we make sure that there are no duplicate signatures
|
||||||
|
if len(set(signatures)) != len(signatures):
|
||||||
|
raise TransactionError("signatures must be unique.")
|
||||||
|
|
||||||
|
# INPUTS: check signatures against pubkey
|
||||||
|
# we expect the signature to be on the pubkey (=message) itself
|
||||||
|
n_sigs_required = n_sigs_required or 1
|
||||||
|
assert n_sigs_required > 0, "n_sigs must be positive."
|
||||||
|
|
||||||
|
# check if enough signatures are present
|
||||||
|
assert (
|
||||||
|
len(signatures) >= n_sigs_required
|
||||||
|
), f"not enough signatures provided: {len(signatures)} < {n_sigs_required}."
|
||||||
|
|
||||||
|
n_valid_sigs_per_output = 0
|
||||||
|
# loop over all signatures in input
|
||||||
|
for input_sig in signatures:
|
||||||
|
for pubkey in pubkeys:
|
||||||
|
logger.trace(f"verifying signature {input_sig} by pubkey {pubkey}.")
|
||||||
|
logger.trace(f"Message: {proof.secret}")
|
||||||
|
if verify_schnorr_signature(
|
||||||
message=proof.secret.encode("utf-8"),
|
message=proof.secret.encode("utf-8"),
|
||||||
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
|
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
|
||||||
signature=bytes.fromhex(signature),
|
signature=bytes.fromhex(input_sig),
|
||||||
):
|
):
|
||||||
# a signature matches
|
n_valid_sigs_per_output += 1
|
||||||
return True
|
logger.trace(
|
||||||
# none of the pubkeys had a match
|
f"signature on input is valid: {input_sig} on {pubkey}."
|
||||||
raise TransactionError("HTLC hash lock signatures did not match.")
|
)
|
||||||
# no pubkeys were included, anyone can spend
|
|
||||||
|
# check if we have enough valid signatures
|
||||||
|
assert n_valid_sigs_per_output, "no valid signature provided for input."
|
||||||
|
assert n_valid_sigs_per_output >= n_sigs_required, (
|
||||||
|
f"signature threshold not met. {n_valid_sigs_per_output} <"
|
||||||
|
f" {n_sigs_required}."
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.trace(
|
||||||
|
f"{n_valid_sigs_per_output} of {n_sigs_required} valid signatures found."
|
||||||
|
)
|
||||||
|
logger.trace("p2pk signature on inputs is valid.")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _verify_input_spending_conditions(self, proof: Proof) -> bool:
|
def _verify_input_spending_conditions(self, proof: Proof) -> bool:
|
||||||
@@ -304,7 +293,7 @@ class LedgerSpendingConditions:
|
|||||||
# loop over all signatures in output
|
# loop over all signatures in output
|
||||||
for sig in p2pksigs:
|
for sig in p2pksigs:
|
||||||
for pubkey in pubkeys:
|
for pubkey in pubkeys:
|
||||||
if verify_p2pk_signature(
|
if verify_schnorr_signature(
|
||||||
message=bytes.fromhex(output.B_),
|
message=bytes.fromhex(output.B_),
|
||||||
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
|
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
|
||||||
signature=bytes.fromhex(sig),
|
signature=bytes.fromhex(sig),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List, Optional
|
from typing import List
|
||||||
|
|
||||||
from ..core.base import HTLCWitness, Proof
|
from ..core.base import HTLCWitness, Proof
|
||||||
from ..core.db import Database
|
from ..core.db import Database
|
||||||
@@ -17,27 +17,31 @@ class WalletHTLC(SupportsDb):
|
|||||||
async def create_htlc_lock(
|
async def create_htlc_lock(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
preimage: Optional[str] = None,
|
preimage: str | None = None,
|
||||||
preimage_hash: Optional[str] = None,
|
preimage_hash: str | None = None,
|
||||||
hashlock_pubkey: Optional[str] = None,
|
hashlock_pubkeys: List[str] | None = None,
|
||||||
locktime_seconds: Optional[int] = None,
|
hashlock_n_sigs: int | None = None,
|
||||||
locktime_pubkey: Optional[str] = None,
|
locktime_seconds: int | None = None,
|
||||||
|
locktime_pubkeys: List[str] | None = None,
|
||||||
) -> HTLCSecret:
|
) -> HTLCSecret:
|
||||||
tags = Tags()
|
tags = Tags()
|
||||||
if locktime_seconds:
|
if locktime_seconds:
|
||||||
tags["locktime"] = str(
|
tags["locktime"] = str(
|
||||||
int((datetime.now() + timedelta(seconds=locktime_seconds)).timestamp())
|
int((datetime.now() + timedelta(seconds=locktime_seconds)).timestamp())
|
||||||
)
|
)
|
||||||
if locktime_pubkey:
|
if locktime_pubkeys:
|
||||||
tags["refund"] = locktime_pubkey
|
tags["refund"] = locktime_pubkeys
|
||||||
|
|
||||||
if not preimage_hash and preimage:
|
if not preimage_hash and preimage:
|
||||||
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
|
||||||
assert preimage_hash, "preimage_hash or preimage must be provided"
|
assert preimage_hash, "preimage_hash or preimage must be provided"
|
||||||
|
|
||||||
if hashlock_pubkey:
|
if hashlock_pubkeys:
|
||||||
tags["pubkeys"] = hashlock_pubkey
|
tags["pubkeys"] = hashlock_pubkeys
|
||||||
|
|
||||||
|
if hashlock_n_sigs:
|
||||||
|
tags["n_sigs"] = str(hashlock_n_sigs)
|
||||||
|
|
||||||
return HTLCSecret(
|
return HTLCSecret(
|
||||||
kind=SecretKind.HTLC.value,
|
kind=SecretKind.HTLC.value,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from ..core.db import Database
|
|||||||
from ..core.p2pk import (
|
from ..core.p2pk import (
|
||||||
P2PKSecret,
|
P2PKSecret,
|
||||||
SigFlags,
|
SigFlags,
|
||||||
sign_p2pk_sign,
|
schnorr_sign,
|
||||||
)
|
)
|
||||||
from ..core.secret import Secret, SecretKind, Tags
|
from ..core.secret import Secret, SecretKind, Tags
|
||||||
from .protocols import SupportsDb, SupportsPrivateKey
|
from .protocols import SupportsDb, SupportsPrivateKey
|
||||||
@@ -21,7 +21,7 @@ from .protocols import SupportsDb, SupportsPrivateKey
|
|||||||
|
|
||||||
class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
||||||
db: Database
|
db: Database
|
||||||
private_key: Optional[PrivateKey] = None
|
private_key: PrivateKey
|
||||||
# ---------- P2PK ----------
|
# ---------- P2PK ----------
|
||||||
|
|
||||||
async def create_p2pk_pubkey(self):
|
async def create_p2pk_pubkey(self):
|
||||||
@@ -61,10 +61,15 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
tags=tags,
|
tags=tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def sign_p2pk_proofs(self, proofs: List[Proof]) -> List[str]:
|
def sign_proofs(self, proofs: List[Proof]) -> List[str]:
|
||||||
assert (
|
"""Signs proof secrets with the private key of the wallet.
|
||||||
self.private_key
|
|
||||||
), "No private key set in settings. Set NOSTR_PRIVATE_KEY in .env"
|
Args:
|
||||||
|
proofs (List[Proof]): Proofs to sign
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: List of signatures for each proof
|
||||||
|
"""
|
||||||
private_key = self.private_key
|
private_key = self.private_key
|
||||||
assert private_key.pubkey
|
assert private_key.pubkey
|
||||||
logger.trace(
|
logger.trace(
|
||||||
@@ -76,7 +81,7 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
logger.trace(f"Signing message: {proof.secret}")
|
logger.trace(f"Signing message: {proof.secret}")
|
||||||
|
|
||||||
signatures = [
|
signatures = [
|
||||||
sign_p2pk_sign(
|
schnorr_sign(
|
||||||
message=proof.secret.encode("utf-8"),
|
message=proof.secret.encode("utf-8"),
|
||||||
private_key=private_key,
|
private_key=private_key,
|
||||||
).hex()
|
).hex()
|
||||||
@@ -85,21 +90,18 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
logger.debug(f"Signatures: {signatures}")
|
logger.debug(f"Signatures: {signatures}")
|
||||||
return signatures
|
return signatures
|
||||||
|
|
||||||
async def sign_p2pk_outputs(self, outputs: List[BlindedMessage]) -> List[str]:
|
def sign_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
|
private_key = self.private_key
|
||||||
assert private_key.pubkey
|
assert private_key.pubkey
|
||||||
return [
|
return [
|
||||||
sign_p2pk_sign(
|
schnorr_sign(
|
||||||
message=bytes.fromhex(output.B_),
|
message=bytes.fromhex(output.B_),
|
||||||
private_key=private_key,
|
private_key=private_key,
|
||||||
).hex()
|
).hex()
|
||||||
for output in outputs
|
for output in outputs
|
||||||
]
|
]
|
||||||
|
|
||||||
async def add_p2pk_witnesses_to_outputs(
|
def add_signature_witnesses_to_outputs(
|
||||||
self, outputs: List[BlindedMessage]
|
self, outputs: List[BlindedMessage]
|
||||||
) -> List[BlindedMessage]:
|
) -> List[BlindedMessage]:
|
||||||
"""Takes a list of outputs and adds a P2PK signatures to each.
|
"""Takes a list of outputs and adds a P2PK signatures to each.
|
||||||
@@ -108,12 +110,12 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
Returns:
|
Returns:
|
||||||
List[BlindedMessage]: Outputs with P2PK signatures added
|
List[BlindedMessage]: Outputs with P2PK signatures added
|
||||||
"""
|
"""
|
||||||
p2pk_signatures = await self.sign_p2pk_outputs(outputs)
|
p2pk_signatures = self.sign_outputs(outputs)
|
||||||
for o, s in zip(outputs, p2pk_signatures):
|
for o, s in zip(outputs, p2pk_signatures):
|
||||||
o.witness = P2PKWitness(signatures=[s]).json()
|
o.witness = P2PKWitness(signatures=[s]).json()
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
async def add_witnesses_to_outputs(
|
def add_witnesses_to_outputs(
|
||||||
self, proofs: List[Proof], outputs: List[BlindedMessage]
|
self, proofs: List[Proof], outputs: List[BlindedMessage]
|
||||||
) -> List[BlindedMessage]:
|
) -> List[BlindedMessage]:
|
||||||
"""Adds witnesses to outputs if the inputs (proofs) indicate an appropriate signature flag
|
"""Adds witnesses to outputs if the inputs (proofs) indicate an appropriate signature flag
|
||||||
@@ -127,25 +129,24 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
# first we check whether all tokens have serialized secrets as their secret
|
# first we check whether all tokens have serialized secrets as their secret
|
||||||
try:
|
try:
|
||||||
for p in proofs:
|
for p in proofs:
|
||||||
Secret.deserialize(p.secret)
|
secret = Secret.deserialize(p.secret)
|
||||||
except Exception:
|
except Exception:
|
||||||
# if not, we do not add witnesses (treat as regular token secret)
|
# if not, we do not add witnesses (treat as regular token secret)
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
# if any of the proofs provided require SIG_ALL, we must provide it
|
# if any of the proofs provided is P2PK and requires SIG_ALL, we must signatures to all outputs
|
||||||
if any(
|
if any(
|
||||||
[
|
[
|
||||||
P2PKSecret.deserialize(p.secret).sigflag == SigFlags.SIG_ALL
|
secret.kind == SecretKind.P2PK.value
|
||||||
|
and P2PKSecret.deserialize(p.secret).sigflag == SigFlags.SIG_ALL
|
||||||
for p in proofs
|
for p in proofs
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
outputs = await self.add_p2pk_witnesses_to_outputs(outputs)
|
outputs = self.add_signature_witnesses_to_outputs(outputs)
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
async def add_p2pk_witnesses_to_proofs(self, proofs: List[Proof]) -> List[Proof]:
|
def add_signature_witnesses_to_proofs(self, proofs: List[Proof]) -> List[Proof]:
|
||||||
p2pk_signatures = await self.sign_p2pk_proofs(proofs)
|
p2pk_signatures = self.sign_proofs(proofs)
|
||||||
logger.debug(f"Unlock signatures for {len(proofs)} proofs: {p2pk_signatures}")
|
|
||||||
logger.debug(f"Proofs: {proofs}")
|
|
||||||
# attach unlock signatures to proofs
|
# attach unlock signatures to proofs
|
||||||
assert len(proofs) == len(p2pk_signatures), "wrong number of signatures"
|
assert len(proofs) == len(p2pk_signatures), "wrong number of signatures"
|
||||||
for p, s in zip(proofs, p2pk_signatures):
|
for p, s in zip(proofs, p2pk_signatures):
|
||||||
@@ -157,14 +158,14 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
p.witness = P2PKWitness(signatures=[s]).json()
|
p.witness = P2PKWitness(signatures=[s]).json()
|
||||||
return proofs
|
return proofs
|
||||||
|
|
||||||
async def add_witnesses_to_proofs(self, proofs: List[Proof]) -> List[Proof]:
|
def add_witnesses_to_proofs(self, proofs: List[Proof]) -> List[Proof]:
|
||||||
"""Adds witnesses to proofs for P2PK redemption.
|
"""Adds witnesses to proofs for P2PK redemption.
|
||||||
|
|
||||||
This method parses the secret of each proof and determines the correct
|
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.
|
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.
|
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.
|
For P2PK and HTLC, we use an individual signature for each token in proofs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
proofs (List[Proof]): List of proofs to add witnesses to
|
proofs (List[Proof]): List of proofs to add witnesses to
|
||||||
@@ -172,22 +173,22 @@ class WalletP2PK(SupportsPrivateKey, SupportsDb):
|
|||||||
Returns:
|
Returns:
|
||||||
List[Proof]: List of proofs with witnesses added
|
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
|
# first we check whether all tokens have serialized secrets as their secret
|
||||||
try:
|
try:
|
||||||
for p in proofs:
|
for p in proofs:
|
||||||
Secret.deserialize(p.secret)
|
secret = Secret.deserialize(p.secret)
|
||||||
except Exception:
|
except Exception:
|
||||||
# if not, we do not add witnesses (treat as regular token secret)
|
# if not, we do not add witnesses (treat as regular token secret)
|
||||||
return proofs
|
return proofs
|
||||||
logger.debug("Spending conditions detected.")
|
logger.debug("Spending conditions detected.")
|
||||||
# P2PK signatures
|
# check if all secrets are either P2PK or HTLC
|
||||||
if all(
|
if all([secret.kind == SecretKind.P2PK.value for p in proofs]):
|
||||||
[Secret.deserialize(p.secret).kind == SecretKind.P2PK.value for p in proofs]
|
proofs = self.add_signature_witnesses_to_proofs(proofs)
|
||||||
):
|
|
||||||
logger.debug("P2PK redemption detected.")
|
# if all([secret.kind == SecretKind.HTLC.value for p in proofs]):
|
||||||
proofs = await self.add_p2pk_witnesses_to_proofs(proofs)
|
# for p in proofs:
|
||||||
|
# htlc_secret = HTLCSecret.deserialize(p.secret)
|
||||||
|
# if htlc_secret.tags.get_tag("pubkeys"):
|
||||||
|
# p = self.add_signature_witnesses_to_proofs([p])[0]
|
||||||
|
|
||||||
return proofs
|
return proofs
|
||||||
|
|||||||
@@ -668,7 +668,7 @@ class Wallet(
|
|||||||
proofs = copy.copy(proofs)
|
proofs = copy.copy(proofs)
|
||||||
|
|
||||||
# potentially add witnesses to unlock provided proofs (if they indicate one)
|
# potentially add witnesses to unlock provided proofs (if they indicate one)
|
||||||
proofs = await self.add_witnesses_to_proofs(proofs)
|
proofs = self.add_witnesses_to_proofs(proofs)
|
||||||
|
|
||||||
input_fees = self.get_fees_for_proofs(proofs)
|
input_fees = self.get_fees_for_proofs(proofs)
|
||||||
logger.debug(f"Input fees: {input_fees}")
|
logger.debug(f"Input fees: {input_fees}")
|
||||||
@@ -700,7 +700,7 @@ class Wallet(
|
|||||||
outputs, rs = self._construct_outputs(amounts, secrets, rs, self.keyset_id)
|
outputs, rs = self._construct_outputs(amounts, secrets, rs, self.keyset_id)
|
||||||
|
|
||||||
# potentially add witnesses to outputs based on what requirement the proofs indicate
|
# potentially add witnesses to outputs based on what requirement the proofs indicate
|
||||||
outputs = await self.add_witnesses_to_outputs(proofs, outputs)
|
outputs = self.add_witnesses_to_outputs(proofs, outputs)
|
||||||
|
|
||||||
# Call swap API
|
# Call swap API
|
||||||
promises = await super().split(proofs, outputs)
|
promises = await super().split(proofs, outputs)
|
||||||
|
|||||||
@@ -120,14 +120,14 @@ async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet):
|
|||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
secret = await wallet1.create_htlc_lock(
|
secret = await wallet1.create_htlc_lock(
|
||||||
preimage=preimage, hashlock_pubkey=pubkey_wallet1
|
preimage=preimage, hashlock_pubkeys=[pubkey_wallet1]
|
||||||
)
|
)
|
||||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
for p in send_proofs:
|
for p in send_proofs:
|
||||||
p.witness = HTLCWitness(preimage=preimage).json()
|
p.witness = HTLCWitness(preimage=preimage).json()
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet2.redeem(send_proofs),
|
wallet2.redeem(send_proofs),
|
||||||
"Mint Error: HTLC no hash lock signatures provided.",
|
"Mint Error: no signatures in proof.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -140,18 +140,18 @@ async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet
|
|||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
secret = await wallet1.create_htlc_lock(
|
secret = await wallet1.create_htlc_lock(
|
||||||
preimage=preimage, hashlock_pubkey=pubkey_wallet1
|
preimage=preimage, hashlock_pubkeys=[pubkey_wallet1]
|
||||||
)
|
)
|
||||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
signatures = await wallet1.sign_p2pk_proofs(send_proofs)
|
signatures = wallet1.sign_proofs(send_proofs)
|
||||||
for p, s in zip(send_proofs, signatures):
|
for p, s in zip(send_proofs, signatures):
|
||||||
p.witness = HTLCWitness(
|
p.witness = HTLCWitness(
|
||||||
preimage=preimage, signature=f"{s[:-5]}11111"
|
preimage=preimage, signatures=[f"{s[:-5]}11111"]
|
||||||
).json() # wrong signature
|
).json() # wrong signature
|
||||||
|
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet2.redeem(send_proofs),
|
wallet2.redeem(send_proofs),
|
||||||
"Mint Error: HTLC hash lock signatures did not match.",
|
"Mint Error: no valid signature provided for input.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -164,17 +164,187 @@ async def test_htlc_redeem_with_correct_signature(wallet1: Wallet, wallet2: Wall
|
|||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
secret = await wallet1.create_htlc_lock(
|
secret = await wallet1.create_htlc_lock(
|
||||||
preimage=preimage, hashlock_pubkey=pubkey_wallet1
|
preimage=preimage, hashlock_pubkeys=[pubkey_wallet1]
|
||||||
)
|
)
|
||||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
signatures = await wallet1.sign_p2pk_proofs(send_proofs)
|
signatures = wallet1.sign_proofs(send_proofs)
|
||||||
for p, s in zip(send_proofs, signatures):
|
for p, s in zip(send_proofs, signatures):
|
||||||
p.witness = HTLCWitness(preimage=preimage, signature=s).json()
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s]).json()
|
||||||
|
|
||||||
await wallet2.redeem(send_proofs)
|
await wallet2.redeem(send_proofs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_htlc_redeem_with_2_of_1_signatures(wallet1: Wallet, wallet2: Wallet):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
preimage = "00000000000000000000000000000000"
|
||||||
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
secret = await wallet1.create_htlc_lock(
|
||||||
|
preimage=preimage,
|
||||||
|
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2],
|
||||||
|
hashlock_n_sigs=1,
|
||||||
|
)
|
||||||
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
|
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||||
|
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||||
|
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||||
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||||
|
|
||||||
|
await wallet2.redeem(send_proofs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_htlc_redeem_with_2_of_2_signatures(wallet1: Wallet, wallet2: Wallet):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
preimage = "00000000000000000000000000000000"
|
||||||
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
secret = await wallet1.create_htlc_lock(
|
||||||
|
preimage=preimage,
|
||||||
|
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2],
|
||||||
|
hashlock_n_sigs=2,
|
||||||
|
)
|
||||||
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
|
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||||
|
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||||
|
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||||
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||||
|
|
||||||
|
await wallet2.redeem(send_proofs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_htlc_redeem_with_2_of_2_signatures_with_duplicate_pubkeys(
|
||||||
|
wallet1: Wallet, wallet2: Wallet
|
||||||
|
):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
preimage = "00000000000000000000000000000000"
|
||||||
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
pubkey_wallet2 = pubkey_wallet1
|
||||||
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
secret = await wallet1.create_htlc_lock(
|
||||||
|
preimage=preimage,
|
||||||
|
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2],
|
||||||
|
hashlock_n_sigs=2,
|
||||||
|
)
|
||||||
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
|
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||||
|
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||||
|
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||||
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||||
|
|
||||||
|
await assert_err(
|
||||||
|
wallet2.redeem(send_proofs),
|
||||||
|
"Mint Error: pubkeys must be unique.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_htlc_redeem_with_3_of_3_signatures_but_only_2_provided(
|
||||||
|
wallet1: Wallet, wallet2: Wallet
|
||||||
|
):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
preimage = "00000000000000000000000000000000"
|
||||||
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
secret = await wallet1.create_htlc_lock(
|
||||||
|
preimage=preimage,
|
||||||
|
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2],
|
||||||
|
hashlock_n_sigs=3,
|
||||||
|
)
|
||||||
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
|
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||||
|
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||||
|
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||||
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||||
|
|
||||||
|
await assert_err(
|
||||||
|
wallet2.redeem(send_proofs),
|
||||||
|
"Mint Error: not enough signatures provided: 2 < 3.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_htlc_redeem_with_2_of_3_signatures_with_2_valid_and_1_invalid_provided(
|
||||||
|
wallet1: Wallet, wallet2: Wallet
|
||||||
|
):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
preimage = "00000000000000000000000000000000"
|
||||||
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
|
privatekey_wallet3 = PrivateKey(secrets.token_bytes(32), raw=True)
|
||||||
|
assert privatekey_wallet3.pubkey
|
||||||
|
pubkey_wallet3 = privatekey_wallet3.pubkey.serialize().hex()
|
||||||
|
|
||||||
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
secret = await wallet1.create_htlc_lock(
|
||||||
|
preimage=preimage,
|
||||||
|
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2, pubkey_wallet3],
|
||||||
|
hashlock_n_sigs=2,
|
||||||
|
)
|
||||||
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
|
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||||
|
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||||
|
signatures3 = [f"{s[:-5]}11111" for s in signatures1] # wrong signature
|
||||||
|
for p, s1, s2, s3 in zip(send_proofs, signatures1, signatures2, signatures3):
|
||||||
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2, s3]).json()
|
||||||
|
|
||||||
|
await wallet2.redeem(send_proofs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_htlc_redeem_with_3_of_3_signatures_with_2_valid_and_1_invalid_provided(
|
||||||
|
wallet1: Wallet, wallet2: Wallet
|
||||||
|
):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
preimage = "00000000000000000000000000000000"
|
||||||
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
|
privatekey_wallet3 = PrivateKey(secrets.token_bytes(32), raw=True)
|
||||||
|
assert privatekey_wallet3.pubkey
|
||||||
|
pubkey_wallet3 = privatekey_wallet3.pubkey.serialize().hex()
|
||||||
|
|
||||||
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
|
secret = await wallet1.create_htlc_lock(
|
||||||
|
preimage=preimage,
|
||||||
|
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2, pubkey_wallet3],
|
||||||
|
hashlock_n_sigs=3,
|
||||||
|
)
|
||||||
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
|
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||||
|
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||||
|
signatures3 = [f"{s[:-5]}11111" for s in signatures1] # wrong signature
|
||||||
|
for p, s1, s2, s3 in zip(send_proofs, signatures1, signatures2, signatures3):
|
||||||
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2, s3]).json()
|
||||||
|
|
||||||
|
await assert_err(
|
||||||
|
wallet2.redeem(send_proofs), "Mint Error: signature threshold not met. 2 < 3."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
||||||
wallet1: Wallet, wallet2: Wallet
|
wallet1: Wallet, wallet2: Wallet
|
||||||
@@ -188,20 +358,20 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
|||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
secret = await wallet1.create_htlc_lock(
|
secret = await wallet1.create_htlc_lock(
|
||||||
preimage=preimage,
|
preimage=preimage,
|
||||||
hashlock_pubkey=pubkey_wallet2,
|
hashlock_pubkeys=[pubkey_wallet2],
|
||||||
locktime_seconds=2,
|
locktime_seconds=2,
|
||||||
locktime_pubkey=pubkey_wallet1,
|
locktime_pubkeys=[pubkey_wallet1],
|
||||||
)
|
)
|
||||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
signatures = await wallet1.sign_p2pk_proofs(send_proofs)
|
signatures = wallet1.sign_proofs(send_proofs)
|
||||||
for p, s in zip(send_proofs, signatures):
|
for p, s in zip(send_proofs, signatures):
|
||||||
p.witness = HTLCWitness(preimage=preimage, signature=s).json()
|
p.witness = HTLCWitness(preimage=preimage, signatures=[s]).json()
|
||||||
|
|
||||||
# should error because we used wallet2 signatures for the hash lock
|
# should error because we used wallet2 signatures for the hash lock
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.redeem(send_proofs),
|
wallet1.redeem(send_proofs),
|
||||||
"Mint Error: HTLC hash lock signatures did not match.",
|
"Mint Error: no valid signature provided for input.",
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
@@ -222,27 +392,27 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_wrong_signature(
|
|||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
secret = await wallet1.create_htlc_lock(
|
secret = await wallet1.create_htlc_lock(
|
||||||
preimage=preimage,
|
preimage=preimage,
|
||||||
hashlock_pubkey=pubkey_wallet2,
|
hashlock_pubkeys=[pubkey_wallet2],
|
||||||
locktime_seconds=2,
|
locktime_seconds=2,
|
||||||
locktime_pubkey=pubkey_wallet1,
|
locktime_pubkeys=[pubkey_wallet1],
|
||||||
)
|
)
|
||||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||||
|
|
||||||
signatures = await wallet1.sign_p2pk_proofs(send_proofs)
|
signatures = wallet1.sign_proofs(send_proofs)
|
||||||
for p, s in zip(send_proofs, signatures):
|
for p, s in zip(send_proofs, signatures):
|
||||||
p.witness = HTLCWitness(
|
p.witness = HTLCWitness(
|
||||||
preimage=preimage, signature=f"{s[:-5]}11111"
|
preimage=preimage, signatures=[f"{s[:-5]}11111"]
|
||||||
).json() # wrong signature
|
).json() # wrong signature
|
||||||
|
|
||||||
# should error because we used wallet2 signatures for the hash lock
|
# should error because we used wallet2 signatures for the hash lock
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.redeem(send_proofs),
|
wallet1.redeem(send_proofs),
|
||||||
"Mint Error: HTLC hash lock signatures did not match.",
|
"Mint Error: no valid signature provided for input.",
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
# should fail since lock time has passed and we provided a wrong signature for timelock
|
# should fail since lock time has passed and we provided a wrong signature for timelock
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.redeem(send_proofs),
|
wallet1.redeem(send_proofs),
|
||||||
"Mint Error: HTLC refund signatures did not match.",
|
"Mint Error: no valid signature provided for input.",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet):
|
|||||||
wallet1.proofs, 8, secret_lock=secret_lock
|
wallet1.proofs, 8, secret_lock=secret_lock
|
||||||
)
|
)
|
||||||
# add signatures of wallet1
|
# add signatures of wallet1
|
||||||
send_proofs = await wallet1.add_p2pk_witnesses_to_proofs(send_proofs)
|
send_proofs = wallet1.add_signature_witnesses_to_proofs(send_proofs)
|
||||||
# here we add the signatures of wallet2
|
# here we add the signatures of wallet2
|
||||||
await wallet2.redeem(send_proofs)
|
await wallet2.redeem(send_proofs)
|
||||||
|
|
||||||
@@ -289,10 +289,10 @@ async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Walle
|
|||||||
wallet1.proofs, 8, secret_lock=secret_lock
|
wallet1.proofs, 8, secret_lock=secret_lock
|
||||||
)
|
)
|
||||||
# add signatures of wallet2 – this is a duplicate signature
|
# add signatures of wallet2 – this is a duplicate signature
|
||||||
send_proofs = await wallet2.add_p2pk_witnesses_to_proofs(send_proofs)
|
send_proofs = wallet2.add_signature_witnesses_to_proofs(send_proofs)
|
||||||
# here we add the signatures of wallet2
|
# here we add the signatures of wallet2
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet2.redeem(send_proofs), "Mint Error: p2pk signatures must be unique."
|
wallet2.redeem(send_proofs), "Mint Error: signatures must be unique."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wal
|
|||||||
wallet1.proofs, 8, secret_lock=secret_lock
|
wallet1.proofs, 8, secret_lock=secret_lock
|
||||||
)
|
)
|
||||||
# add signatures of wallet1
|
# add signatures of wallet1
|
||||||
send_proofs = await wallet1.add_p2pk_witnesses_to_proofs(send_proofs)
|
send_proofs = wallet1.add_signature_witnesses_to_proofs(send_proofs)
|
||||||
# here we add the signatures of wallet2
|
# here we add the signatures of wallet2
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet2.redeem(send_proofs),
|
wallet2.redeem(send_proofs),
|
||||||
@@ -381,7 +381,7 @@ async def test_p2pk_multisig_with_wrong_first_private_key(
|
|||||||
wallet1.proofs, 8, secret_lock=secret_lock
|
wallet1.proofs, 8, secret_lock=secret_lock
|
||||||
)
|
)
|
||||||
# add signatures of wallet1
|
# add signatures of wallet1
|
||||||
send_proofs = await wallet1.add_p2pk_witnesses_to_proofs(send_proofs)
|
send_proofs = wallet1.add_signature_witnesses_to_proofs(send_proofs)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet2.redeem(send_proofs), "Mint Error: signature threshold not met. 1 < 2."
|
wallet2.redeem(send_proofs), "Mint Error: signature threshold not met. 1 < 2."
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user