aboutsummaryrefslogtreecommitdiff
path: root/message/message.go
blob: c50310e0d143b15232993a9565c3e3f197082629 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package message

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/json"
	"fmt"
	"math/bits"

	ipfs "github.com/ipfs/go-ipfs-api"
	pubsub "github.com/libp2p/go-libp2p-pubsub"
)

// Messages on Infodump use a "stamp" using the hashcash algorithm to prevent spam and enable storing messages by importance
// The Message type contains the message itself and a nonce that is used to verify the stamp
type Message struct {
	Message string
	Nonce   int
}

// Proof of Work: Find the nonce for a message by hashing the message and checking for at least n initial zeroes in the binary representation of the resulting hash
func (msg *Message) ProofOfWork(n int) {
	// Create a local copy of the message and start counting
	m := *msg
	m.Nonce = 0
	// Loop until we find a nonce that satisfies the proof of work
	for {
		// Increment the nonce and hash the message
		m.Nonce++
		hash := m.Hash()
		// If the hash has at least n initial zeroes, we have found a valid nonce
		if CountLeadingZeroes(hash) >= n {
			break
		}
	}
	// Set the message to the local copy
	*msg = m
}

// Get the SHA256 hash of a message plus the nonce as a byte slice
func (m *Message) Hash() [32]byte {
	hash := sha256.Sum256([]byte(fmt.Sprintf("%s%d", m.Message, m.Nonce)))
	return hash
}

// Bitwise count leading zeroes in a byte slice
func CountLeadingZeroes(b [32]byte) int {
	count := 0
	for _, v := range b {
		// If the byte is zero, that means there are 8 leading zeroes and we need to continue counting
		if v == 0 {
			count += 8
		} else { // Otherwise, we add the number of leading zeroes in the byte and stop counting
			// Bitwise count leading zeroes in the byte
			count += bits.LeadingZeros8(v)
			// If the byte is not zero, we can stop counting
			break
		}
	}
	return count
}

// Get the stamp of the message
func (m *Message) Stamp() string {
	return fmt.Sprintf("%x", m.Hash())
}

// Lead is a method that returns the number of leading zeroes in the hash of a message plus its nonce
func (m *Message) Lead() int {
	return CountLeadingZeroes(m.Hash())
}

func New(msg string, n int) *Message {
	m := Message{Message: msg}
	m.ProofOfWork(n)
	return &m
}

// Datatype Messages that contains a slice of message.Message that is to be stored on IPFS as JSON
type Messages struct {
	Messages []message.Message `json:"messages"`
}

// JSON encodes the Messages struct into a JSON string
func (m *Messages) JSON() string {
	json, _ := json.Marshal(m)
	return string(json)
}

// Push the JSON string to IPFS and return the hash
func (m *Messages) Push(ipfs *ipfs.IpfsApi) (string, error) {
	hash, err := ipfs.BlockPut(m.JSON())
	return hash, err
}

// Read the JSON string from IPFS and return the Messages struct
func (m *Messages) Read(ipfs *ipfs.IpfsApi, hash string) error {
	json, err := ipfs.BlockGet(hash)
	if err != nil {
		return err
	}
	err = json.Unmarshal([]byte(json), &m)
	return err
}

// Save the Messages struct to the database
func (m *Messages) Save(db *sql.DB) error {
	stmt, err := db.Prepare("INSERT INTO messages (hash) VALUES (?)")
	if err != nil {
		return err
	}
	_, err = stmt.Exec(m.JSON())
	if err != nil {
		return err
	}
	return nil
}

// Push the Messages struct to IPFS and send the hash to the PubSub topic
func (m *Messages) Publish(ipfs *ipfs.IpfsApi, ps *pubsub.PubSub, topic string) error {
	hash, err := m.Push(ipfs)
	if err != nil {
		return err
	}
	err = ps.Publish(topic, []byte(hash))
	return err
}

// ListenAndSave takes an IPFS instance, a PubSub instance, a database and a topic
// Listen on the PubSub topic, look up the hash and read the Messages struct
// On first receipt, save the Messages struct to the database
func ListenAndSave(ipfs *ipfs.IpfsApi, ps *pubsub.PubSub, db *sql.DB, topic string) error {
	// Subscribe to the topic
	sub, err := ps.Subscribe(topic)
	if err != nil {
		return err
	}
	// Listen for messages on the topic
	for {
		msg, err := sub.Next(context.Background())
		if err != nil {
			return err
		}
		// Read the Messages struct from IPFS
		m := Messages{}
		err = m.Read(ipfs, string(msg.Data))
		if err != nil {
			return err
		}
		// Save the Messages struct to the database
		err = m.Save(db)
		if err != nil {
			return err
		}
	}
}