mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
[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:
@@ -9,13 +9,62 @@ from pydantic import BaseModel
|
||||
from .crypto.keys import derive_keys, derive_keyset_id, derive_pubkeys
|
||||
from .crypto.secp import PrivateKey, PublicKey
|
||||
from .legacy import derive_keys_backwards_compatible_insecure_pre_0_12
|
||||
from .p2pk import sign_p2pk_sign
|
||||
|
||||
# ------- PROOFS -------
|
||||
|
||||
|
||||
class SecretKind:
|
||||
P2SH = "P2SH"
|
||||
P2PK = "P2PK"
|
||||
|
||||
|
||||
class Tags(BaseModel):
|
||||
__root__: List[List[str]]
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Secret(BaseModel):
|
||||
"""Describes spending condition encoded in the secret field of a Proof."""
|
||||
|
||||
kind: str
|
||||
data: str
|
||||
nonce: Union[None, str] = None
|
||||
timelock: Union[None, int] = None
|
||||
tags: Union[None, Tags] = None
|
||||
|
||||
def serialize(self) -> str:
|
||||
data_dict: Dict[str, Any] = {
|
||||
"data": self.data,
|
||||
"nonce": self.nonce or PrivateKey().serialize()[:32],
|
||||
}
|
||||
if self.timelock:
|
||||
data_dict["timelock"] = self.timelock
|
||||
if self.tags:
|
||||
data_dict["tags"] = self.tags.__root__
|
||||
logger.debug(
|
||||
json.dumps(
|
||||
[self.kind, data_dict],
|
||||
)
|
||||
)
|
||||
return json.dumps(
|
||||
[self.kind, data_dict],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, data: str):
|
||||
kind, kwargs = json.loads(data)
|
||||
return cls(kind=kind, **kwargs)
|
||||
|
||||
|
||||
class P2SHScript(BaseModel):
|
||||
"""
|
||||
Describes spending condition of a Proof
|
||||
Unlocks P2SH spending condition of a Proof
|
||||
"""
|
||||
|
||||
script: str
|
||||
@@ -34,7 +83,8 @@ class Proof(BaseModel):
|
||||
amount: int = 0
|
||||
secret: str = "" # secret or message to be blinded and signed
|
||||
C: str = "" # signature on secret, unblinded by wallet
|
||||
script: Union[P2SHScript, None] = None # P2SH spending condition
|
||||
p2pksig: Optional[str] = None # P2PK signature
|
||||
p2shscript: Union[P2SHScript, None] = None # P2SH spending condition
|
||||
reserved: Union[
|
||||
None, bool
|
||||
] = False # whether this proof is reserved for sending, used for coin management in the wallet
|
||||
@@ -174,6 +224,19 @@ class PostSplitRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
amount: int
|
||||
outputs: List[BlindedMessage]
|
||||
# signature: Optional[str] = None
|
||||
|
||||
# def sign(self, private_key: PrivateKey):
|
||||
# """
|
||||
# Create a signed split request. The signature is over the `proofs` and `outputs` fields.
|
||||
# """
|
||||
# # message = json.dumps(self.proofs).encode("utf-8") + json.dumps(
|
||||
# # self.outputs
|
||||
# # ).encode("utf-8")
|
||||
# message = json.dumps(self.dict(include={"proofs": ..., "outputs": ...})).encode(
|
||||
# "utf-8"
|
||||
# )
|
||||
# self.signature = sign_p2pk_sign(message, private_key)
|
||||
|
||||
|
||||
class PostSplitResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user