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 }