MakyuuIchaival/contentmessage/message.go

203 lines
4.4 KiB
Go

package contentmessage
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/cloudflare/circl/sign/ed25519"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
"sync"
"time"
)
var messageCacheLimit = 256
var messageCacheMutex sync.RWMutex
var messageCache = make(map[string]*ContentMessage)
func SetMessageCacheLimit(limit int) {
messageCacheLimit = limit
}
type ContentMessage struct {
Version uint64
PublicKey ed25519.PublicKey
IssueTime int64
Identifier cid.Cid
VerificationResult *bool
Signature []byte
}
func NewContentMessageV0(identifier cid.Cid, privateKey ed25519.PrivateKey) ContentMessage {
message := ContentMessage{
Version: 0,
IssueTime: time.Now().UTC().Unix(),
Identifier: identifier,
}
message.Sign(privateKey)
return message
}
func NewContentMessageV1(hash multihash.Multihash, privateKey ed25519.PrivateKey) ContentMessage {
message := ContentMessage{
Version: 1,
IssueTime: time.Now().UTC().Unix(),
Identifier: cid.NewCidV1(cid.Raw, hash),
}
message.Sign(privateKey)
return message
}
func (s *ContentMessage) Sign(privateKey ed25519.PrivateKey) {
s.PublicKey = make([]byte, ed25519.PublicKeySize)
copy(s.PublicKey, privateKey[32:])
s.Signature = ed25519.Sign(privateKey, s.EncodeMessage())
s.VerificationResult = nil
}
func (s *ContentMessage) Verify() (bool, bool) {
currentTime := time.Now()
issueTime := time.Unix(s.IssueTime, 0)
validityStart := issueTime.Add(-time.Hour) //Only one hour before time
validityEnd := issueTime.Add(time.Hour * 24) //Only 24 hours after time
if validityStart.After(currentTime) {
return false, false
}
if validityEnd.Before(currentTime) {
return false, false
}
messageCacheMutex.RLock()
k := string(s.Encode())
cachedMessage, ok := messageCache[k]
messageCacheMutex.RUnlock()
if ok {
return *cachedMessage.VerificationResult, true
}
messageCacheMutex.Lock()
defer messageCacheMutex.Unlock()
if s.VerificationResult == nil {
makeBool := func(v bool) *bool { return &v }
s.VerificationResult = makeBool(ed25519.Verify(s.PublicKey, s.EncodeMessage(), s.Signature))
}
if len(messageCache) >= messageCacheLimit {
//Find oldest value, remove it
var item *ContentMessage
for _, e := range messageCache {
if item == nil || e.IssueTime < item.IssueTime {
item = e
}
}
if item != nil {
delete(messageCache, string(item.Encode()))
}
}
messageCache[k] = s
return *s.VerificationResult, false
}
func (s *ContentMessage) EncodeMessage() []byte {
message := &bytes.Buffer{}
buf := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(buf, s.Version) //signature version
_, _ = message.Write(buf[:n])
if s.Version == 0 || s.Version == 1 {
_, _ = message.Write(s.PublicKey)
n = binary.PutVarint(buf, s.IssueTime) //time
_, _ = message.Write(buf[:n])
if s.Version == 1 {
_, _ = message.Write(s.Identifier.Hash())
} else {
_, _ = s.Identifier.WriteBytes(message)
}
return message.Bytes()
}
return nil
}
func (s *ContentMessage) Encode() []byte {
message := s.EncodeMessage()
if message == nil || len(s.Signature) != ed25519.SignatureSize {
return nil
}
return append(message, s.Signature...)
}
func (s *ContentMessage) String() string {
return fmt.Sprintf("%d %x %d %s %x", s.Version, s.PublicKey, s.IssueTime, s.Identifier.String(), s.Signature)
}
func DecodeContentMessage(signatureBytes []byte) *ContentMessage {
message := ContentMessage{}
buffer := bytes.NewBuffer(signatureBytes)
var err error
message.Version, err = binary.ReadUvarint(buffer)
if err != nil {
return nil
}
if message.Version == 0 || message.Version == 1 {
message.PublicKey = make([]byte, ed25519.PublicKeySize)
_, err := buffer.Read(message.PublicKey)
if err != nil {
return nil
}
message.IssueTime, err = binary.ReadVarint(buffer)
if err != nil {
return nil
}
if message.Version == 1 {
read, mh, err := multihash.MHFromBytes(buffer.Bytes())
if err != nil {
return nil
}
buffer.Next(read)
message.Identifier = cid.NewCidV1(cid.Raw, mh)
} else {
_, message.Identifier, err = cid.CidFromReader(buffer)
if err != nil {
return nil
}
}
message.Signature = make([]byte, ed25519.SignatureSize)
_, err = buffer.Read(message.Signature)
if err != nil {
return nil
}
if buffer.Len() != 0 { //Unknown extra data
return nil
}
return &message
}
return nil
}