Files
nutshell/cashu/core/secret.py
callebtc 7abfc68cfa SIG_ALL signature flag for P2PK (#735)
* n_sigs_refund working, tests added

* update requirements

* wip sigall

* wip

* sigall works

* add signatures for refund

* add mint p2pk tests

* add more p2pk tests

* fix tests

* sign htlc pubkeys as well

* fix htlc and add new test

* fix regtest

* fix new tests with deprecated

* remove asserts

* comments

* new wallet p2pk tests

* getting there

* add more tests

* fixes

* refactor htlc and p2pk validation

* reduce code

* melt with sigall

* fix htlcs

* fix deprecated api tests

* Update cashu/mint/conditions.py

Co-authored-by: lollerfirst <43107113+lollerfirst@users.noreply.github.com>

* refactor sigall validation

---------

Co-authored-by: lollerfirst <43107113+lollerfirst@users.noreply.github.com>
2025-04-25 11:37:19 +02:00

105 lines
3.1 KiB
Python

import json
from enum import Enum
from typing import Any, Dict, List, Optional, Union
from loguru import logger
from pydantic import BaseModel
from .crypto.secp import PrivateKey
class SecretKind(Enum):
P2PK = "P2PK"
HTLC = "HTLC"
class Tags(BaseModel):
"""
Tags are used to encode additional information in the Secret of a Proof.
"""
__root__: List[List[str]] = []
def __init__(self, tags: Optional[List[List[str]]] = None, **kwargs):
super().__init__(**kwargs)
self.__root__ = tags or []
def __setitem__(self, key: str, value: Union[str, List[str]]) -> None:
if isinstance(value, str):
self.__root__.append([key, value])
elif isinstance(value, list):
self.__root__.append([key, *value])
def __getitem__(self, key: str) -> Union[str, None]:
return self.get_tag(key)
def get_tag(self, tag_name: str) -> Union[str, None]:
for tag in self.__root__:
if tag[0] == tag_name:
return tag[1]
return None
def get_tag_int(self, tag_name: str) -> Union[int, None]:
tag = self.get_tag(tag_name)
if tag is not None:
try:
return int(tag)
except ValueError:
logger.warning(f"Tag {tag_name} is not an integer")
return None
def get_tag_all(self, tag_name: str) -> List[str]:
all_tags = []
for tag in self.__root__:
if tag[0] == tag_name:
for t in tag[1:]:
all_tags.append(t)
return all_tags
class Secret(BaseModel):
"""Describes spending condition encoded in the secret field of a Proof."""
kind: str
data: str
tags: Tags
nonce: Union[None, str] = None
def serialize(self) -> str:
data_dict: Dict[str, Any] = {
"data": self.data,
"nonce": self.nonce or PrivateKey().serialize()[:32],
}
if self.tags.__root__:
logger.debug(f"Serializing tags: {self.tags.__root__}")
data_dict["tags"] = self.tags.__root__
return json.dumps(
[self.kind, data_dict],
)
@classmethod
def deserialize(cls, from_proof: str):
kind, kwargs = json.loads(from_proof)
data = kwargs.pop("data")
nonce = kwargs.pop("nonce")
tags_list: List = kwargs.pop("tags", None)
tags = Tags(tags=tags_list)
logger.debug(f"Deserialized Secret: {kind}, {data}, {nonce}, {tags}")
return cls(kind=kind, data=data, nonce=nonce, tags=tags)
def __eq__(self, value: object) -> bool:
# two secrets are equal if they have the same kind, data and tags (ignoring nonce)
if not isinstance(value, Secret):
return False
return (
self.kind == value.kind
and self.data == value.data
and self.tags.__root__ == value.tags.__root__
)
def __hash__(self) -> int:
# everything except nonce
return hash(
(self.kind, self.data, tuple(s for xs in self.tags.__root__ for s in xs))
)