implement auth, closed and close envelopes
This commit is contained in:
36
pkg/encoders/event/canonical.go
Normal file
36
pkg/encoders/event/canonical.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"next.orly.dev/pkg/crypto/sha256"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/encoders/ints"
|
||||
"next.orly.dev/pkg/encoders/text"
|
||||
)
|
||||
|
||||
// ToCanonical converts the event to the canonical encoding used to derive the
|
||||
// event ID.
|
||||
func (ev *E) ToCanonical(dst []byte) (b []byte) {
|
||||
b = dst
|
||||
b = append(b, "[0,\""...)
|
||||
b = hex.EncAppend(b, ev.Pubkey)
|
||||
b = append(b, "\","...)
|
||||
b = ints.New(ev.CreatedAt).Marshal(nil)
|
||||
b = append(b, ',')
|
||||
b = ints.New(ev.Kind).Marshal(nil)
|
||||
b = append(b, ',')
|
||||
b = ev.Tags.Marshal(b)
|
||||
b = append(b, ',')
|
||||
b = text.AppendQuote(b, ev.Content, text.NostrEscape)
|
||||
b = append(b, ']')
|
||||
return
|
||||
}
|
||||
|
||||
// GetIDBytes returns the raw SHA256 hash of the canonical form of an event.E.
|
||||
func (ev *E) GetIDBytes() []byte { return Hash(ev.ToCanonical(nil)) }
|
||||
|
||||
// Hash is a little helper generate a hash and return a slice instead of an
|
||||
// array.
|
||||
func Hash(in []byte) (out []byte) {
|
||||
h := sha256.Sum256(in)
|
||||
return h[:]
|
||||
}
|
||||
@@ -94,17 +94,8 @@ func (ev *E) Free() {
|
||||
ev.b = nil
|
||||
}
|
||||
|
||||
// MarshalJSON marshals an event.E into a JSON byte string.
|
||||
//
|
||||
// Call bufpool.PutBytes(b) to return the buffer to the bufpool after use.
|
||||
//
|
||||
// WARNING: if json.Marshal is called in the hopes of invoking this function on
|
||||
// an event, if it has <, > or * in the content or tags they are escaped into
|
||||
// unicode escapes and break the event ID. Call this function directly in order
|
||||
// to bypass this issue.
|
||||
func (ev *E) MarshalJSON() (b []byte, err error) {
|
||||
b = bufpool.Get()
|
||||
b = b[:0]
|
||||
func (ev *E) Marshal(dst []byte) (b []byte) {
|
||||
b = dst
|
||||
b = append(b, '{')
|
||||
b = append(b, '"')
|
||||
b = append(b, jId...)
|
||||
@@ -159,10 +150,76 @@ func (ev *E) MarshalJSON() (b []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshalls a JSON string into an event.E.
|
||||
// MarshalJSON marshals an event.E into a JSON byte string.
|
||||
//
|
||||
// Call bufpool.PutBytes(b) to return the buffer to the bufpool after use.
|
||||
//
|
||||
// WARNING: if json.Marshal is called in the hopes of invoking this function on
|
||||
// an event, if it has <, > or * in the content or tags they are escaped into
|
||||
// unicode escapes and break the event ID. Call this function directly in order
|
||||
// to bypass this issue.
|
||||
func (ev *E) MarshalJSON() (b []byte, err error) {
|
||||
b = bufpool.Get()
|
||||
b = ev.Marshal(b[:0])
|
||||
// b = b[:0]
|
||||
// b = append(b, '{')
|
||||
// b = append(b, '"')
|
||||
// b = append(b, jId...)
|
||||
// b = append(b, `":"`...)
|
||||
// b = b[:len(b)+2*sha256.Size]
|
||||
// xhex.Encode(b[len(b)-2*sha256.Size:], ev.ID)
|
||||
// b = append(b, `","`...)
|
||||
// b = append(b, jPubkey...)
|
||||
// b = append(b, `":"`...)
|
||||
// b = b[:len(b)+2*schnorr.PubKeyBytesLen]
|
||||
// xhex.Encode(b[len(b)-2*schnorr.PubKeyBytesLen:], ev.Pubkey)
|
||||
// b = append(b, `","`...)
|
||||
// b = append(b, jCreatedAt...)
|
||||
// b = append(b, `":`...)
|
||||
// b = ints.New(ev.CreatedAt).Marshal(b)
|
||||
// b = append(b, `,"`...)
|
||||
// b = append(b, jKind...)
|
||||
// b = append(b, `":`...)
|
||||
// b = ints.New(ev.Kind).Marshal(b)
|
||||
// b = append(b, `,"`...)
|
||||
// b = append(b, jTags...)
|
||||
// b = append(b, `":`...)
|
||||
// if ev.Tags != nil {
|
||||
// b = ev.Tags.Marshal(b)
|
||||
// }
|
||||
// b = append(b, `,"`...)
|
||||
// b = append(b, jContent...)
|
||||
// b = append(b, `":"`...)
|
||||
// // it can happen the slice has insufficient capacity to hold the content AND
|
||||
// // the signature at this point, because the signature encoder must have
|
||||
// // sufficient capacity pre-allocated as it does not append to the buffer.
|
||||
// // unlike every other encoding function up to this point. This also ensures
|
||||
// // that since the bufpool defaults to 1kb, most events won't have a
|
||||
// // re-allocation required, but if they do, it will be this next one, and it
|
||||
// // integrates properly with the buffer pool, reducing GC pressure and
|
||||
// // avoiding new heap allocations.
|
||||
// if cap(b) < len(b)+len(ev.Content)+7+256+2 {
|
||||
// b2 := make([]byte, len(b)+len(ev.Content)*2+7+256+2)
|
||||
// copy(b2, b)
|
||||
// b2 = b2[:len(b)]
|
||||
// // return the old buffer to the pool for reuse.
|
||||
// bufpool.PutBytes(b)
|
||||
// b = b2
|
||||
// }
|
||||
// b = text.NostrEscape(b, ev.Content)
|
||||
// b = append(b, `","`...)
|
||||
// b = append(b, jSig...)
|
||||
// b = append(b, `":"`...)
|
||||
// b = b[:len(b)+2*schnorr.SignatureSize]
|
||||
// xhex.Encode(b[len(b)-2*schnorr.SignatureSize:], ev.Sig)
|
||||
// b = append(b, `"}`...)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal unmarshalls a JSON string into an event.E.
|
||||
//
|
||||
// Call ev.Free() to return the provided buffer to the bufpool afterwards.
|
||||
func (ev *E) UnmarshalJSON(b []byte) (err error) {
|
||||
func (ev *E) Unmarshal(b []byte) (rem []byte, err error) {
|
||||
key := make([]byte, 0, 9)
|
||||
for ; len(b) > 0; b = b[1:] {
|
||||
// Skip whitespace
|
||||
@@ -337,10 +394,7 @@ BetweenKV:
|
||||
log.I.F("between kv")
|
||||
goto eof
|
||||
AfterClose:
|
||||
// Skip any trailing whitespace
|
||||
for len(b) > 0 && isWhitespace(b[0]) {
|
||||
b = b[1:]
|
||||
}
|
||||
rem = b
|
||||
return
|
||||
invalid:
|
||||
err = fmt.Errorf(
|
||||
@@ -353,6 +407,14 @@ eof:
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshalls a JSON string into an event.E.
|
||||
//
|
||||
// Call ev.Free() to return the provided buffer to the bufpool afterwards.
|
||||
func (ev *E) UnmarshalJSON(b []byte) (err error) {
|
||||
_, err = ev.Unmarshal(b)
|
||||
return
|
||||
}
|
||||
|
||||
// isWhitespace returns true if the byte is a whitespace character (space, tab, newline, carriage return).
|
||||
func isWhitespace(b byte) bool {
|
||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
||||
|
||||
48
pkg/encoders/event/signatures.go
Normal file
48
pkg/encoders/event/signatures.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/crypto/p256k"
|
||||
"next.orly.dev/pkg/interfaces/signer"
|
||||
"next.orly.dev/pkg/utils"
|
||||
)
|
||||
|
||||
// Sign the event using the signer.I. Uses github.com/bitcoin-core/secp256k1 if
|
||||
// available for much faster signatures.
|
||||
//
|
||||
// Note that this only populates the Pubkey, ID and Sig. The caller must
|
||||
// set the CreatedAt timestamp as intended.
|
||||
func (ev *E) Sign(keys signer.I) (err error) {
|
||||
ev.Pubkey = keys.Pub()
|
||||
ev.ID = ev.GetIDBytes()
|
||||
if ev.Sig, err = keys.Sign(ev.ID); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Verify an event is signed by the pubkey it contains. Uses
|
||||
// github.com/bitcoin-core/secp256k1 if available for faster verification.
|
||||
func (ev *E) Verify() (valid bool, err error) {
|
||||
keys := p256k.Signer{}
|
||||
if err = keys.InitPub(ev.Pubkey); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if valid, err = keys.Verify(ev.ID, ev.Sig); chk.T(err) {
|
||||
// check that this isn't because of a bogus ID
|
||||
id := ev.GetIDBytes()
|
||||
if !utils.FastEqual(id, ev.ID) {
|
||||
log.E.Ln("event ID incorrect")
|
||||
ev.ID = id
|
||||
err = nil
|
||||
if valid, err = keys.Verify(ev.ID, ev.Sig); chk.E(err) {
|
||||
return
|
||||
}
|
||||
err = errorf.W("event ID incorrect but signature is valid on correct ID")
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user