forked from
Fork 0

135 lines
3.5 KiB

package model
import (
//Group defines and encapsulates Cwtch's conception of group chat. Which are sessions
// tied to a server under a given group key. Each group has a set of messages.
type Group struct {
GroupID string
SignedGroupID []byte
GroupKey [32]byte
GroupServer string
Timeline Timeline
Accepted bool
Owner string
IsCompromised bool
lock sync.Mutex
// NewGroup initializes a new group associated with a given CwtchServer
func NewGroup(server string) *Group {
group := new(Group)
group.GroupServer = server
var groupID [16]byte
if _, err := io.ReadFull(rand.Reader, groupID[:]); err != nil {
group.GroupID = fmt.Sprintf("%x", groupID)
var groupKey [32]byte
if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil {
copy(group.GroupKey[:], groupKey[:])
group.Owner = "self"
return group
// SignGroup adds a signature to the group.
func (g *Group) SignGroup(signature []byte) {
g.SignedGroupID = signature
copy(g.Timeline.SignedGroupID[:], g.SignedGroupID)
// Compromised should be called if we detect a a groupkey leak.
func (g *Group) Compromised() {
g.IsCompromised = true
// Invite generates a invitation that can be sent to a cwtch peer
func (g *Group) Invite() ([]byte, error) {
if g.SignedGroupID == nil {
return nil, errors.New("group isn't signed")
gci := &protocol.GroupChatInvite{
GroupName: g.GroupID,
GroupSharedKey: g.GroupKey[:],
ServerHost: g.GroupServer,
SignedGroupId: g.SignedGroupID[:],
cp := &protocol.CwtchPeerPacket{
GroupChatInvite: gci,
invite, err := proto.Marshal(cp)
return invite, err
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte, verified bool) *Message {
timelineMessage := &Message{
Message: message.GetText(),
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
Received: time.Now(),
Signature: sig,
Verified: verified,
PeerID: message.GetOnion(),
PreviousMessageSig: message.GetPreviousMessageSig(),
return timelineMessage
// GetTimeline provides a safe copy of the timeline-=
func (g *Group) GetTimeline() (t []Message) {
t = g.Timeline.GetMessages()
//EncryptMessage takes a message and encrypts the message under the group key.
func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) []byte {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
wire, err := proto.Marshal(message)
encrypted := secretbox.Seal(nonce[:], []byte(wire), &nonce, &g.GroupKey)
return encrypted
// DecryptMessage takes a ciphertext and returns true and the decrypted message if the
// cipher text can be successfully decrypted,else false.
func (g *Group) DecryptMessage(ciphertext []byte) (bool, *protocol.DecryptedGroupMessage) {
var decryptNonce [24]byte
copy(decryptNonce[:], ciphertext[:24])
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
if ok {
dm := &protocol.DecryptedGroupMessage{}
err := proto.Unmarshal(decrypted, dm)
if err == nil {
return true, dm
return false, nil