[Feature] Initial NIP-26 support (#25)

* adds initial NIP-26 support

* Update README.md

* Update README.md
This commit is contained in:
kdmukai
2023-01-11 16:54:17 -06:00
committed by GitHub
parent 181c1efa23
commit 3a903b77ab
4 changed files with 78 additions and 0 deletions

View File

@@ -90,6 +90,43 @@ while relay_manager.message_pool.has_events():
relay_manager.close_connections()
```
**NIP-26 delegation**
```python
from nostr.delegation import Delegation
from nostr.event import EventKind, Event
from nostr.key import PrivateKey
# Load your "identity" PK that you'd like to keep safely offline
identity_pk = PrivateKey.from_nsec("nsec1...")
# Create a new, disposable PK as the "delegatee" that can be "hot" in a Nostr client
delegatee_pk = PrivateKey()
# the "identity" PK will authorize "delegatee" to sign TEXT_NOTEs on its behalf for the next month
delegation = Delegation(
delegator_pubkey=identity_pk.public_key.hex(),
delegatee_pubkey=delegatee_pk.public_key.hex(),
event_kind=EventKind.TEXT_NOTE,
duration_secs=30*24*60*60
)
identity_pk.sign_delegation(delegation)
event = Event(
delegatee_pk.public_key.hex(),
"Hello, NIP-26!",
tags=[delegation.get_tag()],
)
event.sign(delegatee_pk.hex())
# ...normal broadcast steps...
```
The resulting delegation tag can be stored as plaintext and reused as-is by the "delegatee" PK until the delegation token expires. There is no way to revoke a signed delegation, so current best practice is to keep the expiration time relatively short.
Hopefully clients will include an optional field to store the delegation tag. That would allow the "delegatee" PK to seamlessly post messages on the "identity" key's behalf, while the "identity" key stays safely offline in cold storage.
## Installation
```bash
pip install nostr

32
nostr/delegation.py Normal file
View File

@@ -0,0 +1,32 @@
import time
from dataclasses import dataclass
@dataclass
class Delegation:
delegator_pubkey: str
delegatee_pubkey: str
event_kind: int
duration_secs: int = 30*24*60 # default to 30 days
signature: str = None # set in PrivateKey.sign_delegation
@property
def expires(self) -> int:
return int(time.time()) + self.duration_secs
@property
def conditions(self) -> str:
return f"kind={self.event_kind}&created_at<{self.expires}"
@property
def delegation_token(self) -> str:
return f"nostr:delegation:{self.delegatee_pubkey}:{self.conditions}"
def get_tag(self) -> list[str]:
""" Called by Event """
return [
"delegation",
self.delegator_pubkey,
self.conditions,
self.signature,
]

View File

@@ -4,6 +4,7 @@ from enum import IntEnum
from secp256k1 import PrivateKey, PublicKey
from hashlib import sha256
class EventKind(IntEnum):
SET_METADATA = 0
TEXT_NOTE = 1
@@ -12,6 +13,7 @@ class EventKind(IntEnum):
ENCRYPTED_DIRECT_MESSAGE = 4
DELETE = 5
class Event():
def __init__(
self,

View File

@@ -4,8 +4,12 @@ import secp256k1
from cffi import FFI
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from hashlib import sha256
from nostr.delegation import Delegation
from . import bech32
class PublicKey:
def __init__(self, raw_bytes: bytes) -> None:
self.raw_bytes = raw_bytes
@@ -93,6 +97,9 @@ class PrivateKey:
sk = secp256k1.PrivateKey(self.raw_secret)
sig = sk.schnorr_sign(hash, None, raw=True)
return sig.hex()
def sign_delegation(self, delegation: Delegation) -> None:
delegation.signature = self.sign_message_hash(sha256(delegation.delegation_token.encode()).digest())
def __eq__(self, other):
return self.raw_secret == other.raw_secret