types, pubkey and signature codec

This commit is contained in:
2025-01-30 18:17:59 -01:06
parent 5d58f61011
commit 282fb7e361
14 changed files with 366 additions and 4 deletions

View File

@@ -14,12 +14,12 @@ So, this is how realy events look:
<unix second precision timestamp in decimal ascii>\n
key:value;extra;...\n // zero or more line separated, fields cannot contain a semicolon, end with newline instead of semicolon, key lowercase alphanumeric, first alpha, only key is mandatory, only reserved is `content`
content: // literally this word on one line
<content>\n // any number of further line breaks, last line without break is signature
<bip-340 schnorr signature encoded in URL-base64>
<content>\n // any number of further line breaks, last line is signature
<bip-340 schnorr signature encoded in URL-base64>\n
```
The canonical form is exactly this, except the last linebreak and the base64 encoded signature, hashed using SHA256. There is no need for placing the event ID in the wire format either, so this is also the wire format.
The canonical form is exactly this, except for the signature and following linebreak, hashed with Blake2b
The database stored form of this event should make use of an event ID hash to monotonic collision free serial table and an event table.
Event ID hashes will be encoded in URL-base64 where used in tags or mentioned in content with the prefix `event:`. Public keys must be prefixed with `pubkey:` Tag keys should be intelligible words and a specification for their structure should be befined by users of them and shared with other REALY devs.
Event ID hashes will be encoded in URL-base64 where used in tags or mentioned in content with the prefix `event:`. Public keys must be prefixed with `pubkey:` Tag keys should be intelligible words and a specification for their structure should be defined by users of them and shared with other REALY devs.

13
pkg/codec/codec.go Normal file
View File

@@ -0,0 +1,13 @@
package codec
// C is an interface for encoding and decoding that allows embedding encoders
// inside other encoders by the use of append for Marshal and slice for
// Unmarshal.
type C interface {
// Marshal data by appending it to the provided destination, and return the
// resultant slice.
Marshal(dst []byte) (result []byte, err error)
// Unmarshal the next expected data element from the provided slice and return
// the remainder after the expected separator.
Unmarshal(data []byte) (rem []byte, err error)
}

14
pkg/event/event.go Normal file
View File

@@ -0,0 +1,14 @@
package event
import (
"protocol.realy.lol/pkg/event/types"
)
type Event struct {
Type types.T
Pubkey []byte
Timestamp int64
Tags [][]byte
Content []byte
Signature []byte
}

9
pkg/event/log.go Normal file
View File

@@ -0,0 +1,9 @@
package event
import (
"protocol.realy.lol/pkg/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
)

9
pkg/event/types/log.go Normal file
View File

@@ -0,0 +1,9 @@
package types
import (
"protocol.realy.lol/pkg/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
)

44
pkg/event/types/types.go Normal file
View File

@@ -0,0 +1,44 @@
package types
import (
"io"
)
// A T is a type descriptor, that is terminated by a newline.
type T []byte
// Marshal append the T to a slice and appends a terminal newline, and returns
// the result.
func (t *T) Marshal(dst []byte) (result []byte, err error) {
if t == nil {
return
}
result = append(append(dst, []byte(*t)...), '\n')
return
}
// Unmarshal expects an identifier followed by a newline. If the buffer ends
// without a newline an EOF is returned.
func (t *T) Unmarshal(data []byte) (rem []byte, err error) {
rem = data
if t == nil {
err = errorf.E("can't unmarshal into nil types.T")
return
}
if len(rem) < 2 {
err = errorf.E("can't unmarshal nothing")
return
}
for i := range rem {
if rem[i] == '\n' {
// write read data up to the newline and return the remainder after
// the newline.
*t = rem[:i]
rem = rem[i+1:]
return
}
}
// a T must end with a newline or an io.EOF is returned.
err = io.EOF
return
}

View File

@@ -0,0 +1,28 @@
package types
import (
"bytes"
"testing"
)
func TestT_Marshal_Unmarshal(t *testing.T) {
typ := T("note")
var err error
var res []byte
if res, err = typ.Marshal(nil); chk.E(err) {
t.Fatal(err)
}
log.I.S(res)
t2 := new(T)
var rem []byte
if rem, err = t2.Unmarshal(res); chk.E(err) {
t.Fatal(err)
}
if len(rem) > 0 {
log.I.S(rem)
}
log.I.S(t2)
if !bytes.Equal(typ, *t2) {
t.Fatal("types.T did not encode/decode faithfully")
}
}

9
pkg/pubkey/log.go Normal file
View File

@@ -0,0 +1,9 @@
package pubkey
import (
"protocol.realy.lol/pkg/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
)

74
pkg/pubkey/pubkey.go Normal file
View File

@@ -0,0 +1,74 @@
package pubkey
import (
"bytes"
"crypto/ed25519"
"encoding/base64"
"io"
)
const Len = 44
type P struct{ ed25519.PublicKey }
func New(pk []byte) (p *P, err error) {
if len(pk) != ed25519.PublicKeySize {
err = errorf.E("invalid public key size: %d; require %d",
len(pk), ed25519.PublicKeySize)
return
}
p = &P{pk}
return
}
func (p *P) Marshal(dst []byte) (result []byte, err error) {
result = dst
if p == nil || p.PublicKey == nil || len(p.PublicKey) == 0 {
err = errorf.E("nil/zero length pubkey")
return
}
if len(p.PublicKey) != ed25519.PublicKeySize {
err = errorf.E("invalid public key length %d; require %d '%0x'",
len(p.PublicKey), ed25519.PublicKeySize, p.PublicKey)
return
}
buf := bytes.NewBuffer(result)
w := base64.NewEncoder(base64.URLEncoding, buf)
if _, err = w.Write(p.PublicKey); chk.E(err) {
return
}
if err = w.Close(); chk.E(err) {
return
}
result = append(buf.Bytes(), '\n')
return
}
func (p *P) Unmarshal(data []byte) (rem []byte, err error) {
rem = data
if p == nil {
err = errorf.E("can't unmarshal into nil types.T")
return
}
if len(rem) < 2 {
err = errorf.E("can't unmarshal nothing")
return
}
for i := range rem {
if rem[i] == '\n' {
if i != Len {
err = errorf.E("invalid encoded pubkey length %d; require %d '%0x'",
i, Len, rem[:i])
return
}
p.PublicKey = make([]byte, ed25519.PublicKeySize)
if _, err = base64.URLEncoding.Decode(p.PublicKey, rem[:i]); chk.E(err) {
return
}
rem = rem[i+1:]
return
}
}
err = io.EOF
return
}

38
pkg/pubkey/pubkey_test.go Normal file
View File

@@ -0,0 +1,38 @@
package pubkey
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"testing"
)
func TestP_Marshal_Unmarshal(t *testing.T) {
pk := make([]byte, ed25519.PublicKeySize)
var err error
if _, err = rand.Read(pk); chk.E(err) {
t.Fatal(err)
}
log.I.S(pk)
var p *P
if p, err = New(pk); chk.E(err) {
t.Fatal(err)
}
var o []byte
if o, err = p.Marshal(nil); chk.E(err) {
t.Fatal(err)
}
log.I.F("%d %s", len(o), o)
p2 := &P{}
var rem []byte
if rem, err = p2.Unmarshal(o); chk.E(err) {
t.Fatal(err)
}
if len(rem) > 0 {
log.I.F("%d %s", len(rem), rem)
}
log.I.S(p2.PublicKey)
if !bytes.Equal(pk, p2.PublicKey) {
t.Fatal("public key did not encode/decode faithfully")
}
}

9
pkg/signature/log.go Normal file
View File

@@ -0,0 +1,9 @@
package signature
import (
"protocol.realy.lol/pkg/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
)

View File

@@ -0,0 +1,74 @@
package signature
import (
"bytes"
"crypto/ed25519"
"encoding/base64"
"io"
)
const Len = 88
type S struct{ Signature []byte }
func New(sig []byte) (p *S, err error) {
if len(sig) != ed25519.SignatureSize {
err = errorf.E("invalid signature size: %d; require %d",
len(sig), ed25519.SignatureSize)
return
}
p = &S{sig}
return
}
func (p *S) Marshal(dst []byte) (result []byte, err error) {
result = dst
if p == nil || p.Signature == nil || len(p.Signature) == 0 {
err = errorf.E("nil/zero length signature")
return
}
if len(p.Signature) != ed25519.SignatureSize {
err = errorf.E("invalid signature length %d; require %d '%0x'",
len(p.Signature), ed25519.SignatureSize, p.Signature)
return
}
buf := bytes.NewBuffer(result)
w := base64.NewEncoder(base64.URLEncoding, buf)
if _, err = w.Write(p.Signature); chk.E(err) {
return
}
if err = w.Close(); chk.E(err) {
return
}
result = append(buf.Bytes(), '\n')
return
}
func (p *S) Unmarshal(data []byte) (rem []byte, err error) {
rem = data
if p == nil {
err = errorf.E("can't unmarshal into nil types.T")
return
}
if len(rem) < 2 {
err = errorf.E("can't unmarshal nothing")
return
}
for i := range rem {
if rem[i] == '\n' {
if i != Len {
err = errorf.E("invalid encoded signature length %d; require %d '%0x'",
i, Len, rem[:i])
return
}
p.Signature = make([]byte, ed25519.SignatureSize)
if _, err = base64.URLEncoding.Decode(p.Signature, rem[:i]); chk.E(err) {
return
}
rem = rem[i+1:]
return
}
}
err = io.EOF
return
}

View File

@@ -0,0 +1,38 @@
package signature
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"testing"
)
func TestS_Marshal_Unmarshal(t *testing.T) {
sig := make([]byte, ed25519.SignatureSize)
var err error
if _, err = rand.Read(sig); chk.E(err) {
t.Fatal(err)
}
log.I.S(sig)
var s *S
if s, err = New(sig); chk.E(err) {
t.Fatal(err)
}
var o []byte
if o, err = s.Marshal(nil); chk.E(err) {
t.Fatal(err)
}
log.I.F("%d %s", len(o), o)
p2 := &S{}
var rem []byte
if rem, err = p2.Unmarshal(o); chk.E(err) {
t.Fatal(err)
}
if len(rem) > 0 {
log.I.F("%d %s", len(rem), rem)
}
log.I.S(p2.Signature)
if !bytes.Equal(sig, p2.Signature) {
t.Fatal("signature did not encode/decode faithfully")
}
}

3
repos/readme.md Normal file
View File

@@ -0,0 +1,3 @@
# relays
relay implementations for various subprotocols