elkrem: sync with upstream onehop branch

This commit syncs the version of elkrem currently checked into master
with the version that has been developed in the upstream master branch.
This commit is contained in:
Olaoluwa Osuntokun 2016-06-29 16:54:24 -07:00
parent 96667cbc4a
commit 102a0f6689
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
5 changed files with 157 additions and 268 deletions

@ -26,30 +26,28 @@ discards leaves 0b0000 and 0b0001, as they have the parent node 8.
For total hashes (2**h)-1 requires a tree of height h.
Sender:
as state, must store 1 hash (root) and current index (h bits)
to move to the next index, compute at most h hashes.
as state, must store 1 hash (root) and that's all
generate any index, compute at most h hashes.
Receiver:
as state, must store at most h+1 hashes and the index of each hash (h*(h+1)) bits
to compute a previous index, compute at most h hashes.
*/
const maxIndex = uint64(281474976710654) // 2^48 - 2
const maxHeight = uint8(47)
// You can calculate h from i but I can't figure out how without taking
// O(i) ops. Feels like there should be a clever O(h) way. 1 byte, whatever.
type ElkremNode struct {
h uint8 // height of this node
i uint64 // index (ith node)
i uint64 // index (i'th node)
sha *wire.ShaHash // hash
}
type ElkremSender struct {
treeHeight uint8 // height of tree (size is 2**height -1 )
current uint64 // last sent hash index
maxIndex uint64 // top of the tree
root *wire.ShaHash // root hash of the tree
root *wire.ShaHash // root hash of the tree
}
type ElkremReceiver struct {
treeHeight uint8 // height of tree (size is 2**height -1 )
s []ElkremNode // store of received hashes, max size = height
s []ElkremNode // store of received hashes
}
func LeftSha(in wire.ShaHash) wire.ShaHash {
@ -81,43 +79,22 @@ func descend(w, i uint64, h uint8, sha wire.ShaHash) (wire.ShaHash, error) {
return sha, nil
}
// Creates an Elkrem Sender from a root hash and tree height
func NewElkremSender(th uint8, r wire.ShaHash) ElkremSender {
// Creates an Elkrem Sender from a root hash.
func NewElkremSender(r wire.ShaHash) *ElkremSender {
var e ElkremSender
e.root = &r
e.treeHeight = th
// set max index based on tree height
for j := uint8(0); j <= e.treeHeight; j++ {
e.maxIndex = e.maxIndex<<1 | 1
}
e.maxIndex-- // 1 less than 2**h
return e
return &e
}
// Creates an Elkrem Receiver from a tree height
func NewElkremReceiver(th uint8) ElkremReceiver {
var e ElkremReceiver
e.treeHeight = th
return e
}
// Next() increments the index to the next hash and outputs it
func (e *ElkremSender) Next() (*wire.ShaHash, error) {
sha, err := e.AtIndex(e.current)
if err != nil {
return nil, err
}
// increment index for next time
e.current++
return sha, nil
}
// w is the wanted index, i is the root index
// AtIndex skips to the requested index
// should never error; remove error..?
func (e *ElkremSender) AtIndex(w uint64) (*wire.ShaHash, error) {
out, err := descend(w, e.maxIndex, e.treeHeight, *e.root)
out, err := descend(w, maxIndex, maxHeight, *e.root)
return &out, err
}
// AddNext inserts the next hash in the tree. Returns an error if
// the incoming hash doesn't fit.
func (e *ElkremReceiver) AddNext(sha *wire.ShaHash) error {
// note: careful about atomicity / disk writes here
var n ElkremNode
@ -144,7 +121,12 @@ func (e *ElkremReceiver) AddNext(sha *wire.ShaHash) error {
e.s = append(e.s, n) // append new node to stack
return nil
}
// AtIndex returns the w'th hash in the receiver.
func (e *ElkremReceiver) AtIndex(w uint64) (*wire.ShaHash, error) {
if e == nil || e.s == nil {
return nil, fmt.Errorf("nil elkrem receiver")
}
var out ElkremNode // node we will eventually return
for _, n := range e.s { // go through stack
if w <= n.i { // found one bigger than or equal to what we want
@ -159,3 +141,11 @@ func (e *ElkremReceiver) AtIndex(w uint64) (*wire.ShaHash, error) {
sha, err := descend(w, out.i, out.h, *out.sha)
return &sha, err
}
// UpTo tells you what the receiver can go up to.
func (e *ElkremReceiver) UpTo() uint64 {
if len(e.s) < 1 {
return 0
}
return e.s[len(e.s)-1].i
}

@ -6,13 +6,13 @@ import (
"github.com/roasbeef/btcd/wire"
)
// TestElkremBig makes a height 63 (max size possible) tree and tries 10K hashes
// TestElkremBig tries 10K hashes
func TestElkremBig(t *testing.T) {
sndr := NewElkremSender(63, wire.DoubleSha256SH([]byte("elktest")))
rcv := NewElkremReceiver(63)
SenderSerdesTest(t, &sndr)
sndr := NewElkremSender(wire.DoubleSha256SH([]byte("elktest")))
var rcv ElkremReceiver
// SenderSerdesTest(t, sndr)
for n := uint64(0); n < 10000; n++ {
sha, err := sndr.Next()
sha, err := sndr.AtIndex(n)
if err != nil {
t.Fatal(err)
}
@ -28,7 +28,7 @@ func TestElkremBig(t *testing.T) {
}
}
}
SenderSerdesTest(t, &sndr)
// SenderSerdesTest(t, sndr)
ReceiverSerdesTest(t, &rcv)
for n := uint64(0); n < 10000; n += 500 {
sha, err := rcv.AtIndex(n)
@ -39,12 +39,12 @@ func TestElkremBig(t *testing.T) {
}
}
// TestElkremSmall makes a height 15 (65534 size) tree and tries 10K hashes
func TestElkremSmall(t *testing.T) {
sndr := NewElkremSender(15, wire.DoubleSha256SH([]byte("elktest")))
rcv := NewElkremReceiver(15)
// TestElkremLess tries 10K hashes
func TestElkremLess(t *testing.T) {
sndr := NewElkremSender(wire.DoubleSha256SH([]byte("elktest2")))
var rcv ElkremReceiver
for n := uint64(0); n < 5000; n++ {
sha, err := sndr.Next()
sha, err := sndr.AtIndex(n)
if err != nil {
t.Fatal(err)
}

@ -1,76 +0,0 @@
package elkrem
import (
"bytes"
"fmt"
"github.com/roasbeef/btcutil"
)
/* findpre - find the pre-image for a given hash
The worst (?) has happened and your channel counterparty has broadcast an old,
invalid state. That's bad. But what, that means you get to take all the money.
This is the fun part of the channel enforcement mechanism.
The old transaction they broadcast has an nLockTime field which provides a hint
about which state number, and which hash index, was used. Bitcoin's nLockTime
field counts as blocks from 0 to 499,999,999 (which would happen somtime after
10,000 CE) and as a unix time from 500,000,000 up to 4,294,967,296 (which is
much sooner, in 2106). There is some extra space we can use here in both cases,
though the unix time gives us much more. nLockTimes from 500,000,000 to
1,036,870,912 are safe, representing dates from 1985 to 2002, which are before
any possible bitcoin transaction. 1,036,870,912 is 500,000,000 plus
536,870,912, and 536,870,912 is 2^29, so we have 29 bits of free space.
After subtracting 500,000,000, the remaning lowest 29 bits are the bits of the
index within the elkrem receiver used in that transaction. That way, even with
a trillion previous channel states (2^40) we will only need to search through
2048 possible branches to find the right pre-image. In most cases, there will
be fewer than 536,870,912 previous states and we can seek directly to the
correct pre-image.
*/
func (e *ElkremReceiver) FindPre(
target [20]byte, timeHint uint32) (*[20]byte, error) {
maxUint32 := uint32((1 << 32) - 1)
minTime := uint32(500000000)
hintRange := uint32((1 << 29) - 1)
// a timeHint of 2^32 (4294967296) means we don't have a timeHint.
if timeHint == maxUint32 {
return nil, fmt.Errorf("no timeHint")
}
// valid timeHint range is 500M to 500M + 2^29
if timeHint < minTime || timeHint > minTime+hintRange {
return nil, fmt.Errorf("timeHint %d out of range (500M - ~1G)", timeHint)
}
indexHint := uint64(timeHint - minTime)
maxIndex := e.s[len(e.s)-1].i // highest index we have
if indexHint > maxIndex { // we can't derive needed index
return nil, fmt.Errorf("hint index %d greater than max index %d",
indexHint, maxIndex)
}
// iterate though, adding 2^29 each time.
// there is some redundancy here when you have a large number of guesses
// to go through, so this could be optimized later.
for guess := indexHint; guess < maxIndex; guess += uint64(hintRange) {
sha, err := e.AtIndex(guess) // generate preimage
if err != nil {
return nil, err
}
var truncatedSha [20]byte
copy(truncatedSha[:], sha.Bytes()) // copy into 20 byte array
checkHash := btcutil.Hash160(truncatedSha[:]) // hash and compare
if bytes.Equal(target[:], checkHash) { // matches hash, return
return &truncatedSha, nil
}
}
// got through the loop without finding anything.
return nil, fmt.Errorf("Couldn't find preimage of %x. timeHint %d bad?",
target, timeHint)
}

@ -10,97 +10,27 @@ import (
/* Serialization and Deserialization methods for the Elkrem structs.
Senders turn into 41 byte long slices. Receivers are variable length,
with 41 bytes for each stored hash, up to a maximum of 64. Receivers are
prepended with the total number of hashes, so the total max size is 2625 bytes.
with 41 bytes for each stored hash, up to a maximum of 48. Receivers are
prepended with the total number of hashes, so the total max size is 1969 bytes.
*/
// ToBytes turns the Elkrem Sender into a 41 byte slice:
// first the tree height (1 byte), then 8 byte index of last sent,
// then the 32 byte root sha hash.
func (e *ElkremSender) ToBytes() ([]byte, error) {
var buf bytes.Buffer
// write 1 byte height of tree (size of the whole sender)
err := binary.Write(&buf, binary.BigEndian, e.treeHeight)
if err != nil {
return nil, err
}
// write 8 byte index of current sha (last sent)
err = binary.Write(&buf, binary.BigEndian, e.current)
if err != nil {
return nil, err
}
// write 32 byte sha hash
n, err := buf.Write(e.root.Bytes())
if err != nil {
return nil, err
}
if n != 32 {
return nil, fmt.Errorf("%d byte hash, expect 32", n)
}
return buf.Bytes(), nil
}
// ElkremSenderFromBytes turns a 41 byte slice into a sender, picking up at
// the index where it left off.
func ElkremSenderFromBytes(b []byte) (ElkremSender, error) {
var e ElkremSender
e.root = new(wire.ShaHash)
buf := bytes.NewBuffer(b)
if buf.Len() != 41 {
return e, fmt.Errorf("Got %d bytes for sender, expect 41")
}
// read 1 byte height
err := binary.Read(buf, binary.BigEndian, &e.treeHeight)
if err != nil {
return e, err
}
// read 8 byte index
err = binary.Read(buf, binary.BigEndian, &e.current)
if err != nil {
return e, err
}
// read 32 byte sha root
err = e.root.SetBytes(buf.Next(32))
if err != nil {
return e, err
}
if e.treeHeight < 1 || e.treeHeight > 63 { // check for super high / low tree
return e, fmt.Errorf("Read invalid sender tree height %d", e.treeHeight)
}
for j := uint8(0); j <= e.treeHeight; j++ {
e.maxIndex = e.maxIndex<<1 | 1
}
e.maxIndex--
if e.current > e.maxIndex { // check for index higher than height allows
return e, fmt.Errorf("Sender claims current %d; %d max with height %d",
e.current, e.maxIndex, e.treeHeight)
}
return e, nil
}
// ToBytes turns the Elkrem Receiver into a bunch of bytes in a slice.
// first the tree height (1 byte), then number of nodes (1 byte),
// then a series of 41 byte long serialized nodes,
// which are 1 byte height, 8 byte index, 32 byte hash.
// first the number of nodes (1 byte), then a series of 41 byte long
// serialized nodes, which are 1 byte height, 8 byte index, 32 byte hash.
func (e *ElkremReceiver) ToBytes() ([]byte, error) {
numOfNodes := uint8(len(e.s))
// 0 element receiver also OK. Just an empty slice.
if numOfNodes == 0 {
return nil, fmt.Errorf("Can't serialize empty ElkremReceiver")
return nil, nil
}
if numOfNodes > 64 {
if numOfNodes > maxHeight+1 {
return nil, fmt.Errorf("Broken ElkremReceiver has %d nodes, max 64",
len(e.s))
}
var buf bytes.Buffer // create buffer
// write tree height (1 byte)
err := binary.Write(&buf, binary.BigEndian, e.treeHeight)
if err != nil {
return nil, err
}
// write number of nodes (1 byte)
err = binary.Write(&buf, binary.BigEndian, numOfNodes)
err := binary.Write(&buf, binary.BigEndian, numOfNodes)
if err != nil {
return nil, err
}
@ -115,6 +45,9 @@ func (e *ElkremReceiver) ToBytes() ([]byte, error) {
if err != nil {
return nil, err
}
if node.sha == nil {
return nil, fmt.Errorf("node %d has nil hash", node.i)
}
// write 32 byte sha hash
n, err := buf.Write(node.sha.Bytes())
if err != nil {
@ -124,78 +57,119 @@ func (e *ElkremReceiver) ToBytes() ([]byte, error) {
return nil, fmt.Errorf("%d byte hash, expect 32", n)
}
}
if buf.Len() != (int(numOfNodes)*41)+2 {
if buf.Len() != (int(numOfNodes)*41)+1 {
return nil, fmt.Errorf("Somehow made wrong size buf, got %d expect %d",
buf.Len(), (numOfNodes*41)+2)
buf.Len(), (numOfNodes*41)+1)
}
return buf.Bytes(), nil
}
func ElkremReceiverFromBytes(b []byte) (ElkremReceiver, error) {
func ElkremReceiverFromBytes(b []byte) (*ElkremReceiver, error) {
var e ElkremReceiver
var numOfNodes uint8
if len(b) == 0 { // empty receiver, which is OK
return &e, nil
}
buf := bytes.NewBuffer(b)
// read 1 byte tree height
err := binary.Read(buf, binary.BigEndian, &e.treeHeight)
if err != nil {
return e, err
}
if e.treeHeight < 1 || e.treeHeight > 63 {
return e, fmt.Errorf("Read invalid receiver height: %d", e.treeHeight)
}
var max uint64 // maximum possible given height
for j := uint8(0); j <= e.treeHeight; j++ {
max = max<<1 | 1
}
max--
// read 1 byte number of nodes stored in receiver
err = binary.Read(buf, binary.BigEndian, &numOfNodes)
numOfNodes, err := buf.ReadByte()
if err != nil {
return e, err
return nil, err
}
if numOfNodes < 1 || numOfNodes > 64 {
return e, fmt.Errorf("Read invalid number of nodes: %d", numOfNodes)
if numOfNodes < 1 || numOfNodes > maxHeight+1 {
return nil, fmt.Errorf("Read invalid number of nodes: %d", numOfNodes)
}
if buf.Len() != (int(numOfNodes) * 41) {
return e, fmt.Errorf("Remaining buf wrong size, expect %d got %d",
return nil, fmt.Errorf("Remaining buf wrong size, expect %d got %d",
(numOfNodes * 41), buf.Len())
}
for i := uint8(0); i < numOfNodes; i++ {
var node ElkremNode
node.sha = new(wire.ShaHash)
e.s = make([]ElkremNode, numOfNodes)
for j, _ := range e.s {
e.s[j].sha = new(wire.ShaHash)
// read 1 byte height
err := binary.Read(buf, binary.BigEndian, &node.h)
err := binary.Read(buf, binary.BigEndian, &e.s[j].h)
if err != nil {
return e, err
return nil, err
}
// read 8 byte index
err = binary.Read(buf, binary.BigEndian, &node.i)
err = binary.Read(buf, binary.BigEndian, &e.s[j].i)
if err != nil {
return e, err
return nil, err
}
// read 32 byte sha hash
err = node.sha.SetBytes(buf.Next(32))
err = e.s[j].sha.SetBytes(buf.Next(32))
if err != nil {
return e, err
return nil, err
}
// sanity check. Note that this doesn't check that index and height
// match. Could add that but it's slow.
if node.h > 63 { // check for super high nodes
return e, fmt.Errorf("Read invalid node height %d", node.h)
if e.s[j].h > maxHeight { // check for super high nodes
return nil, fmt.Errorf("Read invalid node height %d", e.s[j].h)
}
if node.i > max { // check for index higher than height allows
return e, fmt.Errorf("Node claims index %d; %d max at height %d",
node.i, max, node.h)
if e.s[j].i > maxIndex { // check for index higher than height allows
return nil, fmt.Errorf("Node claims index %d; %d max at height %d",
e.s[j].i, maxIndex, e.s[j].h)
}
e.s = append(e.s, node)
if i > 0 { // check that node heights are descending
if e.s[i-1].h < e.s[i].h {
return e, fmt.Errorf("Node heights out of order")
if j > 0 { // check that node heights are descending
if e.s[j-1].h < e.s[j].h {
return nil, fmt.Errorf("Node heights out of order")
}
}
}
return e, nil
return &e, nil
}
// There's no real point to the *sender* serialization because
// you just make them from scratch each time. Only thing to save
// is the 32 byte seed and the current index.
// ToBytes turns the Elkrem Sender into a 41 byte slice:
// first the tree height (1 byte), then 8 byte index of last sent,
// then the 32 byte root sha hash.
//func (e *ElkremSender) ToBytes() ([]byte, error) {
// var buf bytes.Buffer
// // write 8 byte index of current sha (last sent)
// err := binary.Write(&buf, binary.BigEndian, e.current)
// if err != nil {
// return nil, err
// }
// // write 32 byte sha hash
// n, err := buf.Write(e.root.Bytes())
// if err != nil {
// return nil, err
// }
// if n != 32 {
// return nil, fmt.Errorf("%d byte hash, expect 32", n)
// }
// return buf.Bytes(), nil
//}
// ElkremSenderFromBytes turns a 41 byte slice into a sender, picking up at
// the index where it left off.
//func ElkremSenderFromBytes(b []byte) (ElkremSender, error) {
// var e ElkremSender
// e.root = new(wire.ShaHash)
// buf := bytes.NewBuffer(b)
// if buf.Len() != 40 {
// return e, fmt.Errorf("Got %d bytes for sender, expect 41")
// }
// // read 8 byte index
// err := binary.Read(buf, binary.BigEndian, &e.current)
// if err != nil {
// return e, err
// }
// // read 32 byte sha root
// err = e.root.SetBytes(buf.Next(32))
// if err != nil {
// return e, err
// }
// if e.current > maxIndex { // check for index higher than height allows
// return e, fmt.Errorf("Sender claims current %d; %d max with height %d",
// e.current, maxIndex, maxHeight)
// }
// return e, nil
//}

@ -10,14 +10,15 @@ func ReceiverSerdesTest(t *testing.T, rcv *ElkremReceiver) {
if err != nil {
t.Fatal(err)
}
t.Logf("rcv2 has len %d\n", len(rcv.s))
t.Logf("Serialized receiver; %d bytes, hex:\n%x\n", len(b), b)
*rcv, err = ElkremReceiverFromBytes(b)
rcv2, err := ElkremReceiverFromBytes(b)
if err != nil {
t.Fatal(err)
}
b2, err := rcv.ToBytes()
t.Logf("rcv2 has len %d\n", len(rcv2.s))
b2, err := rcv2.ToBytes()
if err != nil {
t.Fatal(err)
}
@ -27,24 +28,24 @@ func ReceiverSerdesTest(t *testing.T, rcv *ElkremReceiver) {
}
}
func SenderSerdesTest(t *testing.T, sndr *ElkremSender) {
b, err := sndr.ToBytes()
if err != nil {
t.Fatal(err)
}
t.Logf("Serialized sender; %d bytes, hex:\n%x\n", len(b), b)
//func SenderSerdesTest(t *testing.T, sndr *ElkremSender) {
// b, err := sndr.ToBytes()
// if err != nil {
// t.Fatal(err)
// }
// t.Logf("Serialized sender; %d bytes, hex:\n%x\n", len(b), b)
*sndr, err = ElkremSenderFromBytes(b)
if err != nil {
t.Fatal(err)
}
// *sndr, err = ElkremSenderFromBytes(b)
// if err != nil {
// t.Fatal(err)
// }
b2, err := sndr.ToBytes()
if err != nil {
t.Fatal(err)
}
// b2, err := sndr.ToBytes()
// if err != nil {
// t.Fatal(err)
// }
if !bytes.Equal(b, b2) {
t.Fatalf("First and second serializations different")
}
}
// if !bytes.Equal(b, b2) {
// t.Fatalf("First and second serializations different")
// }
//}