Release v0.3.0

This commit is contained in:
Manu Herrera
2020-11-09 10:05:29 -03:00
parent 4e9aa7a3c5
commit 8107c4478b
1265 changed files with 440488 additions and 107809 deletions

View File

@@ -0,0 +1,167 @@
package shachain
import (
"crypto/sha256"
"errors"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// 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 from the given hash string.
func newElementFromStr(s string, index index) (*element, error) {
hash, err := hashFromString(s)
if err != nil {
return nil, err
}
return &element{
index: index,
hash: *hash,
}, nil
}
// derive computes one shachain element from another by applying a series of
// bit flips and hashing operations based on the starting and ending index.
func (e *element) derive(toIndex index) (*element, error) {
fromIndex := e.index
positions, err := fromIndex.deriveBitTransformations(toIndex)
if err != nil {
return nil, err
}
buf := e.hash.CloneBytes()
for _, position := range positions {
// Flip the bit and then hash the current state.
byteNumber := position / 8
bitNumber := position % 8
buf[byteNumber] ^= (1 << bitNumber)
h := sha256.Sum256(buf)
buf = h[:]
}
hash, err := chainhash.NewHash(buf)
if err != nil {
return nil, err
}
return &element{
index: toIndex,
hash: *hash,
}, nil
}
// isEqual returns true if two elements are identical and false otherwise.
func (e *element) isEqual(e2 *element) bool {
return (e.index == e2.index) &&
(&e.hash).IsEqual(&e2.hash)
}
const (
// 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 known as buckets.
maxHeight uint8 = 48
// rootIndex is an index which corresponds to the root hash.
rootIndex index = 0
)
// startIndex is the index of first element in the shachain PRF.
var startIndex index = (1 << maxHeight) - 1
// 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
// 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 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
// 2. 6(0b110) -> 6,7
// 3. 5(0b101) -> 5
// 4. 4(0b100) -> 4,5,6,7
// 5. 3(0b011) -> 3
// 6. 2(0b010) -> 2, 3
// 7. 1(0b001) -> 1
//
// ^ bucket number
// |
// 3 | x
// | |
// 2 | | x
// | | |
// 1 | | x | x
// | | | | |
// 0 | | x | x | x | x
// | | | | | | | | |
// +---|---|---|---|---|---|---|---|---> index
// 0 1 2 3 4 5 6 7
//
func (from index) deriveBitTransformations(to index) ([]uint8, error) {
var positions []uint8
if from == to {
return positions, nil
}
// + --------------- +
// | № | from | to |
// + -- + ---- + --- +
// | 48 | 1 | 1 |
// | 47 | 0 | 0 | [48-5] - same part of 'from' and 'to'
// | 46 | 0 | 0 | indexes which also is called prefix.
// ....
// | 5 | 1 | 1 |
// | 4 | 0 | 1 | <--- position after which indexes becomes
// | 3 | 0 | 0 | different, after this position
// | 2 | 0 | 1 | bits in 'from' index all should be
// | 1 | 0 | 0 | zeros or such indexes considered to be
// | 0 | 0 | 1 | not derivable.
// + -- + ---- + --- +
zeros := countTrailingZeros(from)
if uint64(from) != getPrefix(to, zeros) {
return nil, errors.New("prefixes are different - indexes " +
"aren't derivable")
}
// The remaining part of 'to' index represents the positions which we
// will use then in order to derive one element from another.
for position := zeros - 1; ; position-- {
if getBit(to, position) == 1 {
positions = append(positions, position)
}
if position == 0 {
break
}
}
return positions, nil
}

View File

@@ -0,0 +1,90 @@
package shachain
import (
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// 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 produces a secret by evaluating using the initial seed and a
// particular index.
AtIndex(uint64) (*chainhash.Hash, error)
// Encode writes a binary serialization of the Producer implementation
// to the passed io.Writer.
Encode(io.Writer) error
}
// 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].
root *element
}
// A compile time check to ensure RevocationProducer implements the Producer
// interface.
var _ Producer = (*RevocationProducer)(nil)
// NewRevocationProducer creates new instance of shachain producer.
func NewRevocationProducer(root chainhash.Hash) *RevocationProducer {
return &RevocationProducer{
root: &element{
index: rootIndex,
hash: root,
}}
}
// 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 {
return nil, err
}
return &RevocationProducer{
root: &element{
index: rootIndex,
hash: *root,
},
}, nil
}
// 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)
element, err := p.root.derive(ind)
if err != nil {
return nil, err
}
return &element.hash, nil
}
// Encode writes a binary serialization of the Producer implementation to the
// passed io.Writer.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) Encode(w io.Writer) error {
_, err := w.Write(p.root.hash[:])
return err
}

View File

@@ -0,0 +1,184 @@
package shachain
import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/go-errors/errors"
)
// Store is an interface which serves as an abstraction over data structure
// 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
// 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)
// 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
// 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 is a concrete implementation of the Store interface. The
// revocation store is able to efficiently store N derived shachain 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 with the
// particular number of trailing zeros.
buckets [maxHeight]element
// index is an available index which will be assigned to the new
// element.
index index
}
// A compile time check to ensure RevocationStore implements the Store
// interface.
var _ Store = (*RevocationStore)(nil)
// NewRevocationStore creates the new shachain store.
func NewRevocationStore() *RevocationStore {
return &RevocationStore{
lenBuckets: 0,
index: startIndex,
}
}
// NewRevocationStoreFromBytes recreates the initial store state from the given
// binary shachain store representation.
func NewRevocationStoreFromBytes(r io.Reader) (*RevocationStore, error) {
store := &RevocationStore{}
if err := binary.Read(r, binary.BigEndian, &store.lenBuckets); err != nil {
return nil, err
}
for i := uint8(0); i < store.lenBuckets; i++ {
var hashIndex index
err := binary.Read(r, binary.BigEndian, &hashIndex)
if err != nil {
return nil, err
}
var nextHash chainhash.Hash
if _, err := io.ReadFull(r, nextHash[:]); err != nil {
return nil, err
}
store.buckets[i] = element{
index: hashIndex,
hash: nextHash,
}
}
if err := binary.Read(r, binary.BigEndian, &store.index); err != nil {
return nil, err
}
return store, nil
}
// LookUp function is used to restore/lookup/fetch the previous secret by its
// index. If secret which corresponds to given index was not previously placed
// in store we will not able to derive it and function will fail.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) LookUp(v uint64) (*chainhash.Hash, error) {
ind := newIndex(v)
// Trying to derive the index from one of the existing buckets elements.
for i := uint8(0); i < store.lenBuckets; i++ {
element, err := store.buckets[i].derive(ind)
if err != nil {
continue
}
return &element.hash, nil
}
return nil, errors.Errorf("unable to derive hash #%v", ind)
}
// 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) AddNextEntry(hash *chainhash.Hash) error {
newElement := &element{
index: store.index,
hash: *hash,
}
bucket := countTrailingZeros(newElement.index)
for i := uint8(0); i < bucket; i++ {
e, err := newElement.derive(store.buckets[i].index)
if err != nil {
return err
}
if !e.isEqual(&store.buckets[i]) {
return errors.New("hash isn't derivable from " +
"previous ones")
}
}
store.buckets[bucket] = *newElement
if bucket+1 > store.lenBuckets {
store.lenBuckets = bucket + 1
}
store.index--
return nil
}
// 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) Encode(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, store.lenBuckets)
if err != nil {
return err
}
for i := uint8(0); i < store.lenBuckets; i++ {
element := store.buckets[i]
err := binary.Write(w, binary.BigEndian, element.index)
if err != nil {
return err
}
if _, err = w.Write(element.hash[:]); err != nil {
return err
}
}
return binary.Write(w, binary.BigEndian, store.index)
}

View File

@@ -0,0 +1,74 @@
package shachain
import (
"encoding/hex"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// getBit return bit on index at position.
func getBit(index index, position uint8) uint8 {
return uint8((uint64(index) >> position) & 1)
}
func getPrefix(index index, position uint8) uint64 {
// + -------------------------- +
// | № | value | mask | return |
// + -- + ----- + ---- + ------ +
// | 63 | 1 | 0 | 0 |
// | 62 | 0 | 0 | 0 |
// | 61 | 1 | 0 | 0 |
// ....
// | 4 | 1 | 0 | 0 |
// | 3 | 1 | 0 | 0 |
// | 2 | 1 | 1 | 1 | <--- position
// | 1 | 0 | 1 | 0 |
// | 0 | 1 | 1 | 1 |
// + -- + ----- + ---- + ------ +
var zero uint64
mask := (zero - 1) - uint64((1<<position)-1)
return (uint64(index) & mask)
}
// countTrailingZeros counts number of trailing zero bits, this function is
// used to determine the number of element bucket.
func countTrailingZeros(index index) uint8 {
var zeros uint8
for ; zeros < maxHeight; zeros++ {
if getBit(index, zeros) != 0 {
break
}
}
return zeros
}
// hashFromString takes a hex-encoded string as input and creates an instance of
// chainhash.Hash. The chainhash.NewHashFromStr function not suitable because
// it reverse the given hash.
func hashFromString(s string) (*chainhash.Hash, error) {
// Return an error if hash string is too long.
if len(s) > chainhash.MaxHashStringSize {
return nil, chainhash.ErrHashStrSize
}
// Hex decoder expects the hash to be a multiple of two.
if len(s)%2 != 0 {
s = "0" + s
}
// Convert string hash to bytes.
buf, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
hash, err := chainhash.NewHash(buf)
if err != nil {
return nil, err
}
return hash, nil
}