shachain: grammer fixes, gofmt conformance, slight interface changes

This commit contains a series of post-merge fixes which include:
  * ToBytes -> Encode
    * As the former version doesn’t _require_ allocation of new slices *
  * Store -> AddNextEntry
    * To communicate to the caller that entries MUST be added
      in order
  * Several grammar, spacing, spelling and gofmt fixes.
This commit is contained in:
Olaoluwa Osuntokun 2017-02-22 19:13:56 -08:00
parent 7048480a4a
commit 2e25787a74
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
7 changed files with 146 additions and 124 deletions

View File

@ -3,18 +3,20 @@ package shachain
import (
"crypto/sha256"
"errors"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
// element represent the entity which contains the hash and
// corresponding to it index. By comparing two indexes we may change hash in
// such way to derive another element.
// element represents the entity which contains the hash and index
// corresponding to it. An element is the output of the shachain PRF. By
// comparing two indexes we're able to mutate the hash in such way to derive
// another element.
type element struct {
index index
hash chainhash.Hash
}
// newElementFromStr creates new element by the given hash string.
// newElementFromStr creates new element from the given hash string.
func newElementFromStr(s string, index index) (*element, error) {
hash, err := hashFromString(s)
if err != nil {
@ -27,8 +29,8 @@ func newElementFromStr(s string, index index) (*element, error) {
}, nil
}
// derive used to get one shachain element from another by applying a series of
// bit flipping and hashing operations based on an index.
// derive computes one shachain element from another by applying a series of
// bit flips and hasing operations based on the starting and ending index.
func (e *element) derive(toIndex index) (*element, error) {
fromIndex := e.index
@ -60,48 +62,48 @@ func (e *element) derive(toIndex index) (*element, error) {
}, nil
}
// isEqual checks elements equality.
// isEqual returns true if two elements are identical and false otherwise.
func (first *element) isEqual(second *element) bool {
return (first.index == second.index) &&
(&first.hash).IsEqual(&second.hash)
}
const (
// maxHeight is used to determine the the maximum allowable index and
// the length of array which should be stored in order to derive all
// previous hashes by index, this array also known as buckets.
// maxHeight is used to determine the maximum allowable index and the
// length of the array required to order to derive all previous hashes
// by index. The entries of this array as also knowns as buckets.
maxHeight uint8 = 48
// rootIndex is an index which corresponds to the root hash.
rootIndex index = 0
)
// startIndex is an index of first element.
// startIndex is the index of first element in the shachain PRF.
var startIndex index = (1 << maxHeight) - 1
// index is an number which identifies the hash number and serve as the way to
// determine which operation under hash we should made in order to derive one
// hash from another. index initialized with start index value and than
// decreases down to zero.
// index is a number which identifies the hash number and serves as a way to
// determine the hashing operation required to derive one hash from another.
// index is initialized with the startIndex and decreases down to zero with
// successive derivations.
type index uint64
// newIndex is used to create index instance. The inner operations with
// index implies that index decreasing from some max number to zero, but for
// newIndex is used to create index instance. The inner operations with index
// implies that index decreasing from some max number to zero, but for
// simplicity and backward compatibility with previous logic it was transformed
// to work in opposite way.
func newIndex(v uint64) index {
return startIndex - index(v)
}
// deriveBitTransformations function checks that 'to' index is derivable from
// 'from' index by checking the indexes prefixes and then returns the bit
// positions where the zeroes should be changed to ones in order for the indexes
// to become the same. This set of bits is needed in order to derive one hash
// from another.
// deriveBitTransformations function checks that the 'to' index is derivable
// from the 'from' index by checking the indexes are prefixes of another. The
// bit positions where the zeroes should be changed to ones in order for the
// indexes to become the same are returned. This set of bits is needed in order
// to derive one hash from another.
//
// NOTE: The index 'to' is derivable from index 'from' iff index 'from' lies
// left and above index 'to' on graph below, for example:
// 1. 7(0b111) -> 7
// 1. 7(0b111) -> 7
// 2. 6(0b110) -> 6,7
// 3. 5(0b101) -> 5
// 4. 4(0b100) -> 4,5,6,7

View File

@ -1,9 +1,10 @@
package shachain
import (
"github.com/go-errors/errors"
"reflect"
"testing"
"github.com/go-errors/errors"
)
// bitsToIndex is a helper function which takes 'n' last bits as input and

View File

@ -1,25 +1,34 @@
package shachain
import (
"io"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
// Producer is an interface which serves as an abstraction over data
// structure responsible for efficient generating the secrets by given index.
// The generation of secrets should be made in such way that secret store
// might efficiently store and retrieve the secrets.
// Producer is an interface which serves as an abstraction over the data
// structure responsible for efficiently generating the secrets for a
// particular index based on a root seed. The generation of secrets should be
// made in such way that secret store might efficiently store and retrieve the
// secrets. This is typically implemented as a tree-based PRF.
type Producer interface {
// AtIndex produce secret by given index.
// AtIndex produces a secret by evaluating using the initial seed and a
// particular index.
AtIndex(uint64) (*chainhash.Hash, error)
// ToBytes convert producer to the binary representation.
ToBytes() ([]byte, error)
// Encode writes a binary serialization of the Producer implementation
// to the passed io.Writer.
Encode(io.Writer) error
}
// RevocationProducer implementation of Producer. This version of shachain
// slightly changed in terms of method naming. Initial concept might be found
// here:
// RevocationProducer is an implementation of Producer interface using the
// shachain PRF construct. Starting with a single 32-byte element generated
// from a CSPRNG, shachain is able to efficiently generate a nearly unbounded
// number of secrets while maintaining a constant amount of storage. The
// original description of shachain can be found here:
// https://github.com/rustyrussell/ccan/blob/master/ccan/crypto/shachain/design.txt
// with supplementary material here:
// https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#per-commitment-secret-requirements
type RevocationProducer struct {
// root is the element from which we may generate all hashes which
// corresponds to the index domain [281474976710655,0].
@ -30,18 +39,18 @@ type RevocationProducer struct {
// interface.
var _ Producer = (*RevocationProducer)(nil)
// NewRevocationProducer create new instance of shachain producer.
func NewRevocationProducer(root *chainhash.Hash) *RevocationProducer {
// NewRevocationProducer creates new instance of shachain producer.
func NewRevocationProducer(root chainhash.Hash) *RevocationProducer {
return &RevocationProducer{
root: &element{
index: rootIndex,
hash: *root,
hash: root,
}}
}
// NewRevocationProducerFromBytes deserialize an instance of a RevocationProducer
// encoded in the passed byte slice, returning a fully initialize instance of a
// RevocationProducer.
// NewRevocationProducerFromBytes deserializes an instance of a
// RevocationProducer encoded in the passed byte slice, returning a fully
// initialized instance of a RevocationProducer.
func NewRevocationProducerFromBytes(data []byte) (*RevocationProducer, error) {
root, err := chainhash.NewHash(data)
if err != nil {
@ -56,7 +65,9 @@ func NewRevocationProducerFromBytes(data []byte) (*RevocationProducer, error) {
}, nil
}
// AtIndex produce secret by given index.
// AtIndex produces a secret by evaluating using the initial seed and a
// particular index.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) AtIndex(v uint64) (*chainhash.Hash, error) {
ind := newIndex(v)
@ -69,8 +80,14 @@ func (p *RevocationProducer) AtIndex(v uint64) (*chainhash.Hash, error) {
return &element.hash, nil
}
// ToBytes convert producer to the binary representation.
// Encode writes a binary serialization of the Producer implementation to the
// passed io.Writer.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) ToBytes() ([]byte, error) {
return p.root.hash.CloneBytes(), nil
func (p *RevocationProducer) Encode(w io.Writer) error {
if _, err := w.Write(p.root.hash[:]); err != nil {
return err
}
return nil
}

View File

@ -1,7 +1,9 @@
package shachain
import (
"bytes"
"testing"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
@ -10,8 +12,7 @@ import (
func TestShaChainProducerRestore(t *testing.T) {
var err error
hash := chainhash.DoubleHashH([]byte("shachaintest"))
seed := &hash
seed := chainhash.DoubleHashH([]byte("shachaintest"))
sender := NewRevocationProducer(seed)
s1, err := sender.AtIndex(0)
@ -19,12 +20,12 @@ func TestShaChainProducerRestore(t *testing.T) {
t.Fatal(err)
}
data, err := sender.ToBytes()
if err != nil {
var b bytes.Buffer
if err := sender.Encode(&b); err != nil {
t.Fatal(err)
}
sender, err = NewRevocationProducerFromBytes(data)
sender, err = NewRevocationProducerFromBytes(b.Bytes())
if err != nil {
t.Fatal(err)
}

View File

@ -1,43 +1,51 @@
package shachain
import (
"bytes"
"encoding/binary"
"io"
"github.com/go-errors/errors"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
// Store is an interface which serves as an abstraction over data structure
// responsible for efficient storing and restoring of hash secrets by given
// responsible for efficiently storing and restoring of hash secrets by given
// indexes.
//
// Description: The Lightning Network wants a chain of (say 1 million)
// unguessable 256 bit values; we generate them and send them one at a time
// to a remote node. We don't want the remote node to have to store all the
// unguessable 256 bit values; we generate them and send them one at a time to
// a remote node. We don't want the remote node to have to store all the
// values, so it's better if they can derive them once they see them.
type Store interface {
// LookUp function is used to restore/lookup/fetch the previous secret
// by its index.
LookUp(uint64) (*chainhash.Hash, error)
// Store is used to store the given sha hash in efficient manner.
Store(*chainhash.Hash) error
// AddNextEntry attempts to store the given hash within its internal
// storage in an efficient manner.
//
// NOTE: The hashes derived from the shachain MUST be inserted in the
// order they're produced by a shachain.Producer.
AddNextEntry(*chainhash.Hash) error
// ToBytes convert store to the binary representation.
ToBytes() ([]byte, error)
// Encode writes a binary serialization of the shachain elements
// currently saved by implementation of shachain.Store to the passed
// io.Writer.
Encode(io.Writer) error
}
// RevocationStore implementation of SecretStore. This version of shachain store
// slightly changed in terms of method naming. Initial concept might be found
// here:
// https://github.com/rustyrussell/ccan/blob/master/ccan/crypto/shachain/design.txt
// RevocationStore is a concrete implementation of the Store interface. The
// revocation store is able to efficiently store N derived shahain elements in
// a space efficient manner with a space complexity of O(log N). The original
// description of the storage methodology can be found here:
// https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#efficient-per-commitment-secret-storage
type RevocationStore struct {
// lenBuckets stores the number of currently active buckets.
lenBuckets uint8
// buckets is an array of elements from which we may derive all previous
// elements, each bucket corresponds to the element index number of
// trailing zeros.
// buckets is an array of elements from which we may derive all
// previous elements, each bucket corresponds to the element with the
// particular number of trailing zeros.
buckets [maxHeight]element
// index is an available index which will be assigned to the new
@ -59,36 +67,32 @@ func NewRevocationStore() *RevocationStore {
// NewRevocationStoreFromBytes recreates the initial store state from the given
// binary shachain store representation.
func NewRevocationStoreFromBytes(data []byte) (*RevocationStore, error) {
var err error
func NewRevocationStoreFromBytes(r io.Reader) (*RevocationStore, error) {
store := &RevocationStore{}
buf := bytes.NewBuffer(data)
err = binary.Read(buf, binary.BigEndian, &store.lenBuckets)
if err != nil {
if err := binary.Read(r, binary.BigEndian, &store.lenBuckets); err != nil {
return nil, err
}
i := uint8(0)
for ; i < store.lenBuckets; i++ {
e := &element{}
err = binary.Read(buf, binary.BigEndian, &e.index)
for i := uint8(0); i < store.lenBuckets; i++ {
var hashIndex index
err := binary.Read(r, binary.BigEndian, &hashIndex)
if err != nil {
return nil, err
}
hash, err := chainhash.NewHash(buf.Next(chainhash.HashSize))
if err != nil {
var nextHash chainhash.Hash
if _, err := io.ReadFull(r, nextHash[:]); err != nil {
return nil, err
}
e.hash = *hash
store.buckets[i] = *e
store.buckets[i] = element{
index: hashIndex,
hash: nextHash,
}
}
err = binary.Read(buf, binary.BigEndian, &store.index)
if err != nil {
if err := binary.Read(r, binary.BigEndian, &store.index); err != nil {
return nil, err
}
@ -116,12 +120,14 @@ func (store *RevocationStore) LookUp(v uint64) (*chainhash.Hash, error) {
return nil, errors.Errorf("unable to derive hash #%v", ind)
}
// Store is used to store the given sha hash in efficient manner. Given hash
// should be computable with previous ones, and derived from the previous index
// otherwise the function will return the error.
// AddNextEntry attempts to store the given hash within its internal storage in
// an efficient manner.
//
// NOTE: The hashes derived from the shachain MUST be inserted in the order
// they're produced by a shachain.Producer.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) Store(hash *chainhash.Hash) error {
func (store *RevocationStore) AddNextEntry(hash *chainhash.Hash) error {
newElement := &element{
index: store.index,
hash: *hash,
@ -150,37 +156,29 @@ func (store *RevocationStore) Store(hash *chainhash.Hash) error {
return nil
}
// ToBytes convert store to the binary representation.
// Encode writes a binary serialization of the shachain elements currently
// saved by implementation of shachain.Store to the passed io.Writer.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) ToBytes() ([]byte, error) {
var buf bytes.Buffer
var err error
err = binary.Write(&buf, binary.BigEndian, store.lenBuckets)
func (store *RevocationStore) Encode(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, store.lenBuckets)
if err != nil {
return nil, err
return err
}
i := uint8(0)
for ; i < store.lenBuckets; i++ {
for i := uint8(0); i < store.lenBuckets; i++ {
element := store.buckets[i]
err = binary.Write(&buf, binary.BigEndian, element.index)
err := binary.Write(w, binary.BigEndian, element.index)
if err != nil {
return nil, err
return err
}
_, err = buf.Write(element.hash.CloneBytes())
if err != nil {
return nil, err
if _, err = w.Write(element.hash[:]); err != nil {
return err
}
}
err = binary.Write(&buf, binary.BigEndian, store.index)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
return binary.Write(w, binary.BigEndian, store.index)
}

View File

@ -1,8 +1,10 @@
package shachain
import (
"github.com/roasbeef/btcd/chaincfg/chainhash"
"bytes"
"testing"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
type testInsert struct {
@ -398,7 +400,7 @@ var tests = []struct {
},
}
// TestSpecificationShaChainInsert is used to check the consistence with
// TestSpecificationShaChainInsert is used to check the consistency with
// specification hash insert function.
func TestSpecificationShaChainInsert(t *testing.T) {
for _, test := range tests {
@ -410,7 +412,7 @@ func TestSpecificationShaChainInsert(t *testing.T) {
t.Fatal(err)
}
if err := receiver.Store(secret); err != nil {
if err := receiver.AddNextEntry(secret); err != nil {
if insert.successful {
t.Fatalf("Failed (%v): error was "+
"received but it shouldn't: "+
@ -428,11 +430,10 @@ func TestSpecificationShaChainInsert(t *testing.T) {
}
}
// TestShaChainStore checks the ability of shachain store to hold the produces
// TestShaChainStore checks the ability of shachain store to hold the produced
// secrets after recovering from bytes data.
func TestShaChainStore(t *testing.T) {
hash := chainhash.DoubleHashH([]byte("shachaintest"))
seed := &hash
seed := chainhash.DoubleHashH([]byte("shachaintest"))
sender := NewRevocationProducer(seed)
receiver := NewRevocationStore()
@ -443,23 +444,23 @@ func TestShaChainStore(t *testing.T) {
t.Fatal(err)
}
if err = receiver.Store(sha); err != nil {
if err = receiver.AddNextEntry(sha); err != nil {
t.Fatal(err)
}
}
data, err := receiver.ToBytes()
if err != nil {
var b bytes.Buffer
if err := receiver.Encode(&b); err != nil {
t.Fatal(err)
}
receiver, err = NewRevocationStoreFromBytes(data)
newReceiver, err := NewRevocationStoreFromBytes(&b)
if err != nil {
t.Fatal(err)
}
for n := uint64(0); n < 10000; n++ {
if _, err := receiver.LookUp(n); err != nil {
if _, err := newReceiver.LookUp(n); err != nil {
t.Fatal(err)
}
}

View File

@ -2,19 +2,21 @@ package shachain
import (
"encoding/hex"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
// changeBit function produce all necessary hash bits flips. You should be
// aware that the bit flipping in this function a bit strange, example:
// changeBit is a functio that function that flips a bit of the hash at a
// particluar bit-index. You should be aware that the bit flipping in this
// function a bit strange, example:
// hash: [0b00000000, 0b00000000, ... 0b00000000]
// 0 1 ... 31
//
// byte: 0 0 0 0 0 0 0 0
// 7 6 5 4 3 2 1 0
//
// By flipping the bit at 7 position you will flip the first bit in hash and
// by flipping the bit at 8 position you will flip the 16 bit in hash.
// By flipping the bit at 7 position you will flip the first bit in hash and by
// flipping the bit at 8 position you will flip the 16 bit in hash.
func changeBit(hash []byte, position uint8) []byte {
byteNumber := position / 8
bitNumber := position % 8
@ -62,11 +64,11 @@ func countTrailingZeros(index index) uint8 {
return zeros
}
// hashFromString takes the string as input an create the wire shahash, the
// initial wire.ShaHash NewShaHashFromStr function not suitable because it
// reverse the given hash.
// hashFromString takes a hex-encoded string as input and creates an instane of
// chainhash.Hash. The chainhash.NewHashFromStr function not suitable because
// it reverse the given hash.
func hashFromString(s string) (*chainhash.Hash, error) {
// Return error if hash string is too long.
// Return an error if hash string is too long.
if len(s) > chainhash.MaxHashStringSize {
return nil, chainhash.ErrHashStrSize
}