mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-21 19:14:19 +01:00
105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
import hashlib
|
|
import time
|
|
from enum import Enum
|
|
from typing import List, Union
|
|
|
|
from loguru import logger
|
|
|
|
from .crypto.secp import PrivateKey, PublicKey
|
|
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 P2PKSecret(Secret):
|
|
@classmethod
|
|
def from_secret(cls, secret: Secret):
|
|
assert SecretKind(secret.kind) == SecretKind.P2PK, "Secret is not a P2PK secret"
|
|
# NOTE: exclude tags in .dict() because it doesn't deserialize it properly
|
|
# need to add it back in manually with tags=secret.tags
|
|
return cls(**secret.dict(exclude={"tags"}), tags=secret.tags)
|
|
|
|
def get_p2pk_pubkey_from_secret(self) -> List[str]:
|
|
"""Gets the P2PK pubkey from a Secret depending on the locktime.
|
|
|
|
If locktime is passed, only the refund pubkeys are returned.
|
|
Else, the pubkeys in the data field and in the 'pubkeys' tag are returned.
|
|
|
|
Args:
|
|
secret (Secret): P2PK Secret in ecash token
|
|
|
|
Returns:
|
|
str: pubkey to use for P2PK, empty string if anyone can spend (locktime passed)
|
|
"""
|
|
# the pubkey in the data field is the pubkey to use for P2PK
|
|
pubkeys: List[str] = [self.data]
|
|
|
|
# get all additional pubkeys from tags for multisig
|
|
pubkeys += self.tags.get_tag_all("pubkeys")
|
|
|
|
# check if locktime is passed and if so, only return refund pubkeys
|
|
now = time.time()
|
|
if self.locktime and self.locktime < now:
|
|
logger.trace(f"p2pk locktime ran out ({self.locktime}<{now}).")
|
|
# check tags if a refund pubkey is present.
|
|
# If yes, we demand the signature to be from the refund pubkey
|
|
return self.tags.get_tag_all("refund")
|
|
|
|
return pubkeys
|
|
|
|
@property
|
|
def locktime(self) -> Union[None, int]:
|
|
locktime = self.tags.get_tag("locktime")
|
|
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
|
|
|
|
|
|
def sign_p2pk_sign(message: bytes, private_key: PrivateKey) -> bytes:
|
|
# ecdsa version
|
|
# signature = private_key.ecdsa_serialize(private_key.ecdsa_sign(message))
|
|
signature = private_key.schnorr_sign(
|
|
hashlib.sha256(message).digest(), None, raw=True
|
|
)
|
|
return signature
|
|
|
|
|
|
def verify_p2pk_signature(message: bytes, pubkey: PublicKey, signature: bytes) -> bool:
|
|
# ecdsa version
|
|
# return pubkey.ecdsa_verify(message, pubkey.ecdsa_deserialize(signature))
|
|
return pubkey.schnorr_verify(
|
|
hashlib.sha256(message).digest(), signature, None, raw=True
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# generate keys
|
|
private_key_bytes = b"12300000000000000000000000000123"
|
|
private_key = PrivateKey(private_key_bytes, raw=True)
|
|
print(private_key.serialize())
|
|
public_key = private_key.pubkey
|
|
assert public_key
|
|
print(public_key.serialize().hex())
|
|
|
|
# sign message (=pubkey)
|
|
message = public_key.serialize()
|
|
signature = private_key.ecdsa_serialize(private_key.ecdsa_sign(message))
|
|
print(signature.hex())
|
|
|
|
# verify
|
|
pubkey_verify = PublicKey(message, raw=True)
|
|
print(public_key.ecdsa_verify(message, pubkey_verify.ecdsa_deserialize(signature)))
|