go-nostr/nip46/dynamic-signer.go

247 lines
7.1 KiB
Go
Raw Permalink Normal View History

2023-12-01 16:15:24 -03:00
package nip46
import (
"context"
2023-12-01 16:15:24 -03:00
"fmt"
2024-02-08 16:33:39 -03:00
"slices"
"sync"
2024-02-08 16:33:39 -03:00
2023-12-01 16:15:24 -03:00
"github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
"github.com/nbd-wtf/go-nostr/nip44"
2023-12-01 16:15:24 -03:00
)
2024-01-09 16:55:00 -03:00
var _ Signer = (*DynamicSigner)(nil)
2023-12-01 16:15:24 -03:00
2024-01-09 16:55:00 -03:00
type DynamicSigner struct {
2023-12-01 16:15:24 -03:00
sessionKeys []string
sessions []Session
2024-01-09 16:55:00 -03:00
sync.Mutex
2023-12-01 16:15:24 -03:00
getHandlerSecretKey func(handlerPubkey string) (string, error)
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error)
authorizeSigning func(event nostr.Event, from string, secret string) bool
authorizeEncryption func(from string, secret string) bool
onEventSigned func(event nostr.Event)
getRelays func(pubkey string) map[string]RelayReadWrite
2023-12-01 16:15:24 -03:00
}
2024-01-09 16:55:00 -03:00
func NewDynamicSigner(
// the handler is the keypair we use to communicate with the NIP-46 client, decrypt requests, encrypt responses etc
getHandlerSecretKey func(handlerPubkey string) (string, error),
// this should correspond to the actual user on behalf of which we will respond to requests
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error),
// this is called on every sign_event call, if it is nil it will be assumed that everything is authorized
authorizeSigning func(event nostr.Event, from string, secret string) bool,
// this is called on every encrypt or decrypt calls, if it is nil it will be assumed that everything is authorized
authorizeEncryption func(from string, secret string) bool,
// unless it is nil, this is called after every event is signed
onEventSigned func(event nostr.Event),
// unless it is nil, the results of this will be used in reply to get_relays
getRelays func(pubkey string) map[string]RelayReadWrite,
2024-01-09 16:55:00 -03:00
) DynamicSigner {
return DynamicSigner{
getHandlerSecretKey: getHandlerSecretKey,
getUserKeyer: getUserKeyer,
2024-05-15 16:00:30 -03:00
authorizeSigning: authorizeSigning,
authorizeEncryption: authorizeEncryption,
onEventSigned: onEventSigned,
getRelays: getRelays,
2024-01-09 16:55:00 -03:00
}
2023-12-01 16:15:24 -03:00
}
2024-01-09 16:55:00 -03:00
func (p *DynamicSigner) GetSession(clientPubkey string) (Session, bool) {
2023-12-01 16:15:24 -03:00
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey)
if exists {
2024-01-09 16:55:00 -03:00
return p.sessions[idx], true
2023-12-01 16:15:24 -03:00
}
2024-01-09 16:55:00 -03:00
return Session{}, false
}
2023-12-01 16:15:24 -03:00
2024-01-09 16:55:00 -03:00
func (p *DynamicSigner) setSession(clientPubkey string, session Session) {
p.Lock()
defer p.Unlock()
2023-12-01 16:15:24 -03:00
2024-01-09 16:55:00 -03:00
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey)
if exists {
return
2023-12-01 16:15:24 -03:00
}
// add to pool
p.sessionKeys = append(p.sessionKeys, "") // bogus append just to increase the capacity
p.sessions = append(p.sessions, Session{})
copy(p.sessionKeys[idx+1:], p.sessionKeys[idx:])
copy(p.sessions[idx+1:], p.sessions[idx:])
p.sessionKeys[idx] = clientPubkey
p.sessions[idx] = session
}
func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
2024-01-09 16:55:00 -03:00
req Request,
resp Response,
eventResponse nostr.Event,
err error,
) {
2023-12-01 16:15:24 -03:00
if event.Kind != nostr.KindNostrConnect {
return req, resp, eventResponse,
2023-12-01 17:20:29 -03:00
fmt.Errorf("event kind is %d, but we expected %d", event.Kind, nostr.KindNostrConnect)
2023-12-01 16:15:24 -03:00
}
handler := event.Tags.GetFirst([]string{"p", ""})
if handler == nil || !nostr.IsValid32ByteHex((*handler)[1]) {
return req, resp, eventResponse, fmt.Errorf("invalid \"p\" tag")
2023-12-01 16:15:24 -03:00
}
handlerPubkey := (*handler)[1]
handlerSecret, err := p.getHandlerSecretKey(handlerPubkey)
2023-12-01 16:15:24 -03:00
if err != nil {
return req, resp, eventResponse, fmt.Errorf("no private key for %s: %w", handlerPubkey, err)
}
userKeyer, err := p.getUserKeyer(handlerPubkey)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to get user keyer for %s: %w", handlerPubkey, err)
2024-01-09 16:55:00 -03:00
}
var session Session
idx, exists := slices.BinarySearch(p.sessionKeys, event.PubKey)
if exists {
session = p.sessions[idx]
} else {
session = Session{}
session.SharedKey, err = nip04.ComputeSharedSecret(event.PubKey, handlerSecret)
2024-01-09 16:55:00 -03:00
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err)
2024-01-09 16:55:00 -03:00
}
2024-05-20 09:20:39 -03:00
session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err)
}
session.PublicKey, err = userKeyer.GetPublicKey(ctx)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to get public key: %w", err)
}
2024-01-09 16:55:00 -03:00
p.setSession(event.PubKey, session)
}
2024-01-09 16:55:00 -03:00
req, err = session.ParseRequest(event)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("error parsing request: %w", err)
2023-12-01 16:15:24 -03:00
}
var secret string
var result string
2023-12-01 16:15:24 -03:00
var resultErr error
switch req.Method {
case "connect":
if len(req.Params) >= 2 {
secret = req.Params[1]
}
result = "ack"
2023-12-01 16:15:24 -03:00
case "get_public_key":
result = session.PublicKey
2023-12-01 16:15:24 -03:00
case "sign_event":
if len(req.Params) != 1 {
resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'")
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
evt := nostr.Event{}
err = easyjson.Unmarshal([]byte(req.Params[0]), &evt)
2023-12-01 16:15:24 -03:00
if err != nil {
resultErr = fmt.Errorf("failed to decode event/2: %w", err)
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
if p.authorizeSigning != nil && !p.authorizeSigning(evt, event.PubKey, secret) {
2024-01-09 16:55:00 -03:00
resultErr = fmt.Errorf("refusing to sign this event")
break
}
err = userKeyer.SignEvent(ctx, &evt)
2023-12-01 16:15:24 -03:00
if err != nil {
resultErr = fmt.Errorf("failed to sign event: %w", err)
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
jrevt, _ := easyjson.Marshal(evt)
result = string(jrevt)
2023-12-01 16:15:24 -03:00
case "get_relays":
if p.getRelays == nil {
jrelays, _ := json.Marshal(p.getRelays(session.PublicKey))
result = string(jrelays)
} else {
result = "{}"
}
case "nip44_encrypt":
2023-12-01 16:15:24 -03:00
if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'")
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
thirdPartyPubkey := req.Params[0]
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
2023-12-01 16:15:24 -03:00
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string")
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {
2024-01-09 16:55:00 -03:00
resultErr = fmt.Errorf("refusing to encrypt")
break
}
plaintext := req.Params[1]
2024-05-15 16:00:30 -03:00
ciphertext, err := userKeyer.Encrypt(ctx, plaintext, thirdPartyPubkey)
2023-12-01 16:15:24 -03:00
if err != nil {
resultErr = fmt.Errorf("failed to encrypt: %w", err)
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
result = ciphertext
case "nip44_decrypt":
2023-12-01 16:15:24 -03:00
if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'")
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
thirdPartyPubkey := req.Params[0]
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
2023-12-01 16:15:24 -03:00
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string")
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {
2024-01-09 16:55:00 -03:00
resultErr = fmt.Errorf("refusing to decrypt")
break
}
ciphertext := req.Params[1]
2024-05-15 16:00:30 -03:00
plaintext, err := userKeyer.Decrypt(ctx, ciphertext, thirdPartyPubkey)
if err != nil {
resultErr = fmt.Errorf("failed to decrypt: %w", err)
2023-12-01 17:20:29 -03:00
break
2023-12-01 16:15:24 -03:00
}
result = plaintext
2024-10-01 19:10:05 +09:00
case "ping":
result = "pong"
2023-12-01 16:15:24 -03:00
default:
return req, resp, eventResponse,
2023-12-01 17:20:29 -03:00
fmt.Errorf("unknown method '%s'", req.Method)
2023-12-01 16:15:24 -03:00
}
2023-12-01 17:20:29 -03:00
resp, eventResponse, err = session.MakeResponse(req.ID, event.PubKey, result, resultErr)
if err != nil {
return req, resp, eventResponse, err
}
err = eventResponse.Sign(handlerSecret)
if err != nil {
return req, resp, eventResponse, err
}
return req, resp, eventResponse, err
2023-12-01 16:15:24 -03:00
}