mirror of https://github.com/status-im/megolm.git
126 lines
2.9 KiB
Go
126 lines
2.9 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
SHA256_BLOCK_LENGTH = 64
|
||
|
SHA256_OUTPUT_LENGTH = 32
|
||
|
MEGOLM_RATCHET_PART_LENGTH = 32
|
||
|
MEGOLM_RATCHET_PARTS = 4
|
||
|
MEGOLM_RATCHET_LENGTH = (MEGOLM_RATCHET_PARTS * MEGOLM_RATCHET_PART_LENGTH)
|
||
|
OLM_PROTOCOL_VERSION = 3
|
||
|
)
|
||
|
|
||
|
var HASH_KEY_SEEDS = [MEGOLM_RATCHET_PARTS]byte{0x00, 0x01, 0x02, 0x03}
|
||
|
|
||
|
type Megolm struct {
|
||
|
data [][]byte
|
||
|
counter int
|
||
|
}
|
||
|
|
||
|
func NewMegolm(initialData []byte, initialCounter int) (*Megolm, error) {
|
||
|
if len(initialData) != MEGOLM_RATCHET_LENGTH {
|
||
|
return nil, fmt.Errorf("megolm initial data must be %d bytes. Got %d.", MEGOLM_RATCHET_LENGTH, len(initialData))
|
||
|
}
|
||
|
|
||
|
data := make([][]byte, MEGOLM_RATCHET_PARTS)
|
||
|
for i := 0; i < MEGOLM_RATCHET_PARTS; i++ {
|
||
|
data[i] = make([]byte, MEGOLM_RATCHET_PART_LENGTH)
|
||
|
start := i * MEGOLM_RATCHET_PART_LENGTH
|
||
|
copy(data[i], initialData[start:start+MEGOLM_RATCHET_PART_LENGTH])
|
||
|
}
|
||
|
|
||
|
return &Megolm{
|
||
|
data: data,
|
||
|
counter: initialCounter,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (m *Megolm) Data() []byte {
|
||
|
data := make([]byte, MEGOLM_RATCHET_LENGTH)
|
||
|
for i := 0; i < MEGOLM_RATCHET_PARTS; i++ {
|
||
|
start := i * MEGOLM_RATCHET_PART_LENGTH
|
||
|
copy(data[start:start+MEGOLM_RATCHET_PART_LENGTH], m.data[i])
|
||
|
}
|
||
|
|
||
|
return data
|
||
|
}
|
||
|
|
||
|
func (m *Megolm) Advance() {
|
||
|
mask := 0x00FFFFFF
|
||
|
h := 0
|
||
|
m.counter++
|
||
|
|
||
|
/* figure out how much we need to rekey */
|
||
|
for h < MEGOLM_RATCHET_PARTS {
|
||
|
if m.counter&mask == 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
h++
|
||
|
mask >>= 8
|
||
|
}
|
||
|
|
||
|
// update R[h:3] based on h
|
||
|
for i := MEGOLM_RATCHET_PARTS - 1; i >= h; i-- {
|
||
|
m.rehashPart(h, i)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (m *Megolm) AdvanceTo(advanceTo int) {
|
||
|
/* starting with R0, see if we need to update each part of the hash */
|
||
|
for j := 0; j < MEGOLM_RATCHET_PARTS; j++ {
|
||
|
shift := (MEGOLM_RATCHET_PARTS - j - 1) * 8
|
||
|
mask := 0xffffffff << shift
|
||
|
|
||
|
/* how many times do we need to rehash this part?
|
||
|
*
|
||
|
* '& 0xff' ensures we handle integer wraparound correctly
|
||
|
*/
|
||
|
steps :=
|
||
|
((advanceTo >> shift) - (m.counter >> shift)) & 0xff
|
||
|
|
||
|
if steps == 0 {
|
||
|
/* deal with the edge case where megolm->counter is slightly larger
|
||
|
* than advanceTo. This should only happen for R(0), and implies
|
||
|
* that advanceTo has wrapped around and we need to advance R(0)
|
||
|
* 256 times.
|
||
|
*/
|
||
|
if advanceTo < m.counter {
|
||
|
steps = 0x100
|
||
|
} else {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* for all but the last step, we can just bump R(j) without regard
|
||
|
* to R(j+1)...R(3).
|
||
|
*/
|
||
|
for steps > 1 {
|
||
|
m.rehashPart(j, j)
|
||
|
steps--
|
||
|
}
|
||
|
|
||
|
/* on the last step we also need to bump R(j+1)...R(3).
|
||
|
*
|
||
|
* (Theoretically, we could skip bumping R(j+2) if we're going to bump
|
||
|
* R(j+1) again, but the code to figure that out is a bit baroque and
|
||
|
* doesn't save us much).
|
||
|
*/
|
||
|
for k := 3; k >= j; k-- {
|
||
|
m.rehashPart(j, k)
|
||
|
}
|
||
|
m.counter = advanceTo & mask
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (m *Megolm) rehashPart(fromPart, toPart int) {
|
||
|
newPart := HMACSHA256(
|
||
|
m.data[fromPart],
|
||
|
[]byte{HASH_KEY_SEEDS[toPart]},
|
||
|
)
|
||
|
m.data[toPart] = newPart
|
||
|
}
|