shachain: complete shachain implementation

This commit is contained in:
Andrey Samokhvalov 2016-12-14 01:59:48 +03:00 committed by Olaoluwa Osuntokun
parent 3a647869b6
commit b40afeaa08
9 changed files with 1279 additions and 182 deletions

165
shachain/element.go Normal file
View File

@ -0,0 +1,165 @@
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.
type element struct {
index index
hash chainhash.Hash
}
// newElementFromStr creates new element by 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 used to get one shachain element from another by applying a series of
// bit flipping and hashing operations based on an 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 checks elements equality.
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 uint8 = 48
// rootIndex is an index which corresponds to the root hash.
rootIndex index = 0
)
// startIndex is an index of first element.
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.
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 '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.
//
// 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
}

255
shachain/element_test.go Normal file
View File

@ -0,0 +1,255 @@
package shachain
import (
"github.com/go-errors/errors"
"reflect"
"testing"
)
// bitsToIndex is a helper function which takes 'n' last bits as input and
// create shachain index.
// Example:
// Input: 0,1,1,0,0
// Output: 0b000000000000000000000000000000000000000[01100] == 12
func bitsToIndex(bs ...uint64) (index, error) {
if len(bs) > 64 {
return 0, errors.New("number of elements should be lower then" +
" 64")
}
var res uint64 = 0
for i, e := range bs {
if e != 1 && e != 0 {
return 0, errors.New("wrong element, should be '0' or" +
" '1'")
}
res += e * 1 << uint(len(bs)-i-1)
}
return index(res), nil
}
type deriveTest struct {
name string
from index
to index
position []uint8
shouldFail bool
}
func generateTests(t *testing.T) []deriveTest {
var (
tests []deriveTest
from index
to index
err error
)
from, err = bitsToIndex(0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "zero 'from' 'to'",
from: from,
to: to,
position: nil,
shouldFail: false,
})
from, err = bitsToIndex(0, 1, 0, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(0, 1, 0, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "same indexes #1",
from: from,
to: to,
position: nil,
shouldFail: false,
})
from, err = bitsToIndex(1)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "same indexes #2",
from: from,
to: to,
shouldFail: true,
})
from, err = bitsToIndex(0, 0, 0, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(0, 0, 1, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "test seed 'from'",
from: from,
to: to,
position: []uint8{1},
shouldFail: false,
})
from, err = bitsToIndex(1, 1, 0, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(0, 1, 0, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "not the same indexes",
from: from,
to: to,
shouldFail: true,
})
from, err = bitsToIndex(1, 0, 1, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(1, 0, 0, 0)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "'from' index greater then 'to' index",
from: from,
to: to,
shouldFail: true,
})
from, err = bitsToIndex(1)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
to, err = bitsToIndex(1)
if err != nil {
t.Fatalf("can't generate from index: %v", err)
}
tests = append(tests, deriveTest{
name: "zero number trailing zeros",
from: from,
to: to,
position: nil,
shouldFail: false,
})
return tests
}
// TestDeriveIndex check the correctness of index derive function by testing
// the index corner cases.
func TestDeriveIndex(t *testing.T) {
for _, test := range generateTests(t) {
pos, err := test.from.deriveBitTransformations(test.to)
if err != nil {
if !test.shouldFail {
t.Fatalf("Failed (%v): %v", test.name, err)
}
} else {
if test.shouldFail {
t.Fatalf("Failed (%v): test should failed "+
"but it's not", test.name)
}
if !reflect.DeepEqual(pos, test.position) {
t.Fatalf("Failed(%v): position is wrong real:"+
"%v expected:%v", test.name, pos, test.position)
}
}
t.Logf("Passed: %v", test.name)
}
}
var deriveElementTests = []struct {
name string
index index
output string
seed string
shouldFail bool
}{
{
name: "generate_from_seed FF alternate bits 1",
index: 0xaaaaaaaaaaa,
output: "56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528",
seed: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
shouldFail: false,
},
{
name: "generate_from_seed FF alternate bits 2",
index: 0x555555555555,
output: "9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31",
seed: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
shouldFail: false,
},
{
name: "generate_from_seed 01 last nontrivial node",
index: 1,
output: "915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c",
seed: "0101010101010101010101010101010101010101010101010101010101010101",
shouldFail: false,
},
}
// TestSpecificationDeriveElement is used to check the consistency with
// specification hash derivation function.
func TestSpecificationDeriveElement(t *testing.T) {
for _, test := range deriveElementTests {
// Generate seed element.
element, err := newElementFromStr(test.seed, rootIndex)
if err != nil {
t.Fatal(err)
}
// Derive element by index.
result, err := element.derive(test.index)
if err != nil {
if !test.shouldFail {
t.Fatalf("Failed (%v): %v", test.name, err)
}
} else {
if test.shouldFail {
t.Fatalf("Failed (%v): test should failed "+
"but it's not", test.name)
}
// Generate element which we should get after deriviation.
output, err := newElementFromStr(test.output, test.index)
if err != nil {
t.Fatal(err)
}
// Check that they are equal.
if !result.isEqual(output) {
t.Fatalf("Failed (%v): hash is wrong, real:"+
"%v expected:%v", test.name,
result.hash.String(), output.hash.String())
}
}
t.Logf("Passed (%v)", test.name)
}
}

76
shachain/producer.go Normal file
View File

@ -0,0 +1,76 @@
package shachain
import (
"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.
type Producer interface {
// AtIndex produce secret by given index.
AtIndex(uint64) (*chainhash.Hash, error)
// ToBytes convert producer to the binary representation.
ToBytes() ([]byte, error)
}
// RevocationProducer implementation of Producer. This version of shachain
// 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
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 create new instance of shachain producer.
func NewRevocationProducer(root *chainhash.Hash) *RevocationProducer {
return &RevocationProducer{
root: &element{
index: rootIndex,
hash: *root,
}}
}
// NewRevocationProducerFromBytes deserialize an instance of a RevocationProducer
// encoded in the passed byte slice, returning a fully initialize 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 produce secret by given 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
}
// ToBytes convert producer to the binary representation.
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) ToBytes() ([]byte, error) {
return p.root.hash.CloneBytes(), nil
}

40
shachain/producer_test.go Normal file
View File

@ -0,0 +1,40 @@
package shachain
import (
"testing"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
// TestShaChainProducerRestore checks the ability of shachain producer to be
// properly recreated from binary representation.
func TestShaChainProducerRestore(t *testing.T) {
var err error
hash := chainhash.DoubleHashH([]byte("shachaintest"))
seed := &hash
sender := NewRevocationProducer(seed)
s1, err := sender.AtIndex(0)
if err != nil {
t.Fatal(err)
}
data, err := sender.ToBytes()
if err != nil {
t.Fatal(err)
}
sender, err = NewRevocationProducerFromBytes(data)
if err != nil {
t.Fatal(err)
}
s3, err := sender.AtIndex(0)
if err != nil {
t.Fatal(err)
}
if !s1.IsEqual(s3) {
t.Fatalf("secrets should match: %v:%v", s1.String(), s3.String())
}
}

View File

@ -1,181 +0,0 @@
package shachain
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"sync"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcutil"
)
const (
maxIndex = 1<<64 - 1
)
// chainFragment...
type chainBranch struct {
index uint64
hash [32]byte
}
// HyperShaChain...
// * https://github.com/rustyrussell/ccan/blob/master/ccan/crypto/shachain/design.txt
type HyperShaChain struct {
sync.RWMutex
lastChainIndex uint64
numValid uint64
chainBranches [64]chainBranch
lastHash chainhash.Hash
}
// NewHyperShaChain
// * used to track their preimages
func New() *HyperShaChain {
return &HyperShaChain{lastChainIndex: 0, numValid: 0}
}
// NewHyperShaChainFromSeed...
// * used to derive your own preimages
func NewFromSeed(seed *[32]byte, deriveTo uint64) (*HyperShaChain, error) {
var shaSeed [32]byte
// If no seed is specified, generate a new one.
if seed == nil {
_, err := rand.Read(shaSeed[:])
if err != nil {
return nil, err
}
} else {
shaSeed = *seed
}
// The last possible value in the chain is our starting index.
start := uint64(maxIndex)
stop := deriveTo
curHash := derive(start, stop, shaSeed)
// TODO(roasbeef): from/to or static size?
return &HyperShaChain{lastChainIndex: deriveTo, lastHash: curHash}, nil
}
// derive...
func derive(from, to uint64, startingHash [32]byte) [32]byte {
nextHash := startingHash
numBranches := from ^ to
// The number of branches we need to derive is log2(numBranches)
toDerive := 0
for ; numBranches>>uint(toDerive) > 0; toDerive++ {
}
toDerive-- // needed?
for i := int(toDerive - 1); i >= 0; i-- {
if (numBranches>>uint(i))&1 == 1 {
// Flip the ith bit, then hash the current state to
// advance down the tree.
nextHash[i/8] ^= (1 << (uint(i) % 8))
nextHash = sha256.Sum256(nextHash[:])
}
}
return nextHash
}
// canDerive...
func canDerive(from, to uint64) bool {
return ^from&to == 1
}
// getHash...
// index should be commitment #
func (h *HyperShaChain) GetHash(index uint64) (*[32]byte, error) {
for i := uint64(0); i < h.numValid; i++ {
/* If we can get from key to index only by resetting bits,
* we can derive from it => index has no bits key doesn't. */
if !canDerive(h.chainBranches[i].index, index) {
continue
}
nextHash := derive(h.chainBranches[i].index, index,
h.chainBranches[i].hash)
return &nextHash, nil
}
return nil, fmt.Errorf("unable to derive hash # %v", index)
}
// addHash
func (h *HyperShaChain) AddNextHash(hash [32]byte) error {
// Hashes for a remote chain must be added in order.
nextIdx := h.lastChainIndex + 1
if nextIdx != h.lastChainIndex+1 || nextIdx == 0 && h.numValid != 0 {
return fmt.Errorf("shachain values must be added in order, attempted"+
"to add index %v, chain is at %v", nextIdx, h.lastChainIndex)
}
i := uint64(0)
for ; i < h.numValid; i++ {
if canDerive(nextIdx, h.chainBranches[i].index) {
// Ensure we can actually derive this value.
derivation := derive(nextIdx, h.chainBranches[i].index, hash)
if !bytes.Equal(derivation[:], h.chainBranches[i].hash[:]) {
// TODO(roasbeef): better err message
return fmt.Errorf("chain corruption")
}
break
}
}
h.chainBranches[i].index = nextIdx
copy(h.chainBranches[i].hash[:], hash[:])
copy(h.lastHash[:], hash[:])
h.numValid = i + 1
h.lastChainIndex = nextIdx
return nil
}
// CurrentPreImage...
func (h *HyperShaChain) CurrentPreImage() *chainhash.Hash {
h.RLock()
defer h.RUnlock()
return &h.lastHash
}
// CurrentRevocationHash...
// TODO(roasbeef): *chainhash.Hash vs [wire.HashSize]byte ?
func (h *HyperShaChain) CurrentRevocationHash() []byte {
h.RLock()
defer h.RUnlock()
return btcutil.Hash160(h.lastHash[:])
}
// LocatePreImage...
// Alice just broadcasted an old commitment tx, we need the revocation hash to
// claim the funds so we don't get cheated. However, we aren't storing all the
// preimages in memory. So which shachain index # did she broadcast?
func (h *HyperShaChain) LocatePreImage(outputScript []byte) (uint64, *[32]byte) {
// TODO(roasbeef): parallel goroutine divide and conquer?
// * need to know which side it is? also proper keys?
// * guess and check till script template matches the p2sh hash
return 0, nil
}
// MarshallBinary...
func (h *HyperShaChain) Encode(b io.Writer) error {
return nil
}
// UnmarshallBinary...
func (h *HyperShaChain) Decode(b io.Reader) error {
return nil
}

View File

@ -1 +0,0 @@
package shachain

186
shachain/store.go Normal file
View File

@ -0,0 +1,186 @@
package shachain
import (
"bytes"
"encoding/binary"
"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
// 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)
// Store is used to store the given sha hash in efficient manner.
Store(*chainhash.Hash) error
// ToBytes convert store to the binary representation.
ToBytes() ([]byte, 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
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 [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(data []byte) (*RevocationStore, error) {
var err error
store := &RevocationStore{}
buf := bytes.NewBuffer(data)
err = binary.Read(buf, binary.BigEndian, &store.lenBuckets)
if err != nil {
return nil, err
}
i := uint8(0)
for ; i < store.lenBuckets; i++ {
e := &element{}
err = binary.Read(buf, binary.BigEndian, &e.index)
if err != nil {
return nil, err
}
hash, err := chainhash.NewHash(buf.Next(chainhash.HashSize))
if err != nil {
return nil, err
}
e.hash = *hash
store.buckets[i] = *e
}
err = binary.Read(buf, binary.BigEndian, &store.index)
if 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)
}
// 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.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) Store(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 deriavable from " +
"previous ones")
}
}
store.buckets[bucket] = *newElement
if bucket+1 > store.lenBuckets {
store.lenBuckets = bucket + 1
}
store.index--
return nil
}
// ToBytes convert store to the binary representation.
// 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)
if err != nil {
return nil, err
}
i := uint8(0)
for ; i < store.lenBuckets; i++ {
element := store.buckets[i]
err = binary.Write(&buf, binary.BigEndian, element.index)
if err != nil {
return nil, err
}
_, err = buf.Write(element.hash.CloneBytes())
if err != nil {
return nil, err
}
}
err = binary.Write(&buf, binary.BigEndian, store.index)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

466
shachain/store_test.go Normal file
View File

@ -0,0 +1,466 @@
package shachain
import (
"github.com/roasbeef/btcd/chaincfg/chainhash"
"testing"
)
type testInsert struct {
index index
secret string
successful bool
}
var tests = []struct {
name string
inserts []testInsert
}{
{
name: "insert_secret correct sequence",
inserts: []testInsert{
{
index: 281474976710655,
secret: "7cc854b54e3e0dcdb010d7a3fee464a9687b" +
"e6e8db3be6854c475621e007a5dc",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659c1" +
"a8b4b5bec0c4b872abeba4cb8964",
successful: true,
},
{
index: 281474976710653,
secret: "2273e227a5b7449b6e70f1fb4652864038b1" +
"cbf9cd7c043a7d6456b7fc275ad8",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22ab2" +
"1e9b506fd4998a51d54502e99116",
successful: true,
},
{
index: 281474976710651,
secret: "c65716add7aa98ba7acb236352d665cab173" +
"45fe45b55fb879ff80e6bd0c41dd",
successful: true,
},
{
index: 281474976710650,
secret: "969660042a28f32d9be17344e09374b37996" +
"2d03db1574df5a8a5a47e19ce3f2",
successful: true,
},
{
index: 281474976710649,
secret: "a5a64476122ca0925fb344bdc1854c1c0a59" +
"fc614298e50a33e331980a220f32",
successful: true,
},
{
index: 281474976710648,
secret: "05cde6323d949933f7f7b78776bcc1ea6d9b" +
"31447732e3802e1f7ac44b650e17",
successful: true,
},
},
},
{
name: "insert_secret #1 incorrect",
inserts: []testInsert{
{
index: 281474976710655,
secret: "02a40c85b6f28da08dfdbe0926c53fab2d" +
"e6d28c10301f8f7c4073d5e42e3148",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659" +
"c1a8b4b5bec0c4b872abeba4cb8964",
successful: false,
},
},
},
{
name: "insert_secret #2 incorrect (#1 derived from incorrect)",
inserts: []testInsert{
{
index: 281474976710655,
secret: "02a40c85b6f28da08dfdbe0926c53fab2de6" +
"d28c10301f8f7c4073d5e42e3148",
successful: true,
},
{
index: 281474976710654,
secret: "dddc3a8d14fddf2b68fa8c7fbad274827493" +
"7479dd0f8930d5ebb4ab6bd866a3",
successful: true,
},
{
index: 281474976710653,
secret: "2273e227a5b7449b6e70f1fb4652864038b1" +
"cbf9cd7c043a7d6456b7fc275ad8",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22a" +
"b21e9b506fd4998a51d54502e99116",
successful: false,
},
},
},
{
name: "insert_secret #3 incorrect",
inserts: []testInsert{
{
index: 281474976710655,
secret: "7cc854b54e3e0dcdb010d7a3fee464a9687b" +
"e6e8db3be6854c475621e007a5dc",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659c1" +
"a8b4b5bec0c4b872abeba4cb8964",
successful: true,
},
{
index: 281474976710653,
secret: "c51a18b13e8527e579ec56365482c62f180b" +
"7d5760b46e9477dae59e87ed423a",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22ab2" +
"1e9b506fd4998a51d54502e99116",
successful: false,
},
},
},
{
name: "insert_secret #4 incorrect (1,2,3 derived from incorrect)",
inserts: []testInsert{
{
index: 281474976710655,
secret: "02a40c85b6f28da08dfdbe0926c53fab2de6" +
"d28c10301f8f7c4073d5e42e3148",
successful: true,
},
{
index: 281474976710654,
secret: "dddc3a8d14fddf2b68fa8c7fbad274827493" +
"7479dd0f8930d5ebb4ab6bd866a3",
successful: true,
},
{
index: 281474976710653,
secret: "c51a18b13e8527e579ec56365482c62f18" +
"0b7d5760b46e9477dae59e87ed423a",
successful: true,
},
{
index: 281474976710652,
secret: "ba65d7b0ef55a3ba300d4e87af29868f39" +
"4f8f138d78a7011669c79b37b936f4",
successful: true,
},
{
index: 281474976710651,
secret: "c65716add7aa98ba7acb236352d665cab1" +
"7345fe45b55fb879ff80e6bd0c41dd",
successful: true,
},
{
index: 281474976710650,
secret: "969660042a28f32d9be17344e09374b379" +
"962d03db1574df5a8a5a47e19ce3f2",
successful: true,
},
{
index: 281474976710649,
secret: "a5a64476122ca0925fb344bdc1854c1c0a" +
"59fc614298e50a33e331980a220f32",
successful: true,
},
{
index: 281474976710649,
secret: "05cde6323d949933f7f7b78776bcc1ea6d9b" +
"31447732e3802e1f7ac44b650e17",
successful: false,
},
},
},
{
name: "insert_secret #5 incorrect",
inserts: []testInsert{
{
index: 281474976710655,
secret: "7cc854b54e3e0dcdb010d7a3fee464a9687b" +
"e6e8db3be6854c475621e007a5dc",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659c1a" +
"8b4b5bec0c4b872abeba4cb8964",
successful: true,
},
{
index: 281474976710653,
secret: "2273e227a5b7449b6e70f1fb4652864038b1" +
"cbf9cd7c043a7d6456b7fc275ad8",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22ab21" +
"e9b506fd4998a51d54502e99116",
successful: true,
},
{
index: 281474976710651,
secret: "631373ad5f9ef654bb3dade742d09504c567" +
"edd24320d2fcd68e3cc47e2ff6a6",
successful: true,
},
{
index: 281474976710650,
secret: "969660042a28f32d9be17344e09374b37996" +
"2d03db1574df5a8a5a47e19ce3f2",
successful: false,
},
},
},
{
name: "insert_secret #6 incorrect (5 derived from incorrect)",
inserts: []testInsert{
{
index: 281474976710655,
secret: "7cc854b54e3e0dcdb010d7a3fee464a9687b" +
"e6e8db3be6854c475621e007a5dc",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659c1a" +
"8b4b5bec0c4b872abeba4cb8964",
successful: true,
},
{
index: 281474976710653,
secret: "2273e227a5b7449b6e70f1fb4652864038b1" +
"cbf9cd7c043a7d6456b7fc275ad8",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22ab21" +
"e9b506fd4998a51d54502e99116",
successful: true,
},
{
index: 281474976710651,
secret: "631373ad5f9ef654bb3dade742d09504c567" +
"edd24320d2fcd68e3cc47e2ff6a6",
successful: true,
},
{
index: 281474976710650,
secret: "b7e76a83668bde38b373970155c868a65330" +
"4308f9896692f904a23731224bb1",
successful: true,
},
{
index: 281474976710649,
secret: "a5a64476122ca0925fb344bdc1854c1c0a59f" +
"c614298e50a33e331980a220f32",
successful: true,
},
{
index: 281474976710648,
secret: "05cde6323d949933f7f7b78776bcc1ea6d9b" +
"31447732e3802e1f7ac44b650e17",
successful: false,
},
},
},
{
name: "insert_secret #7 incorrect",
inserts: []testInsert{
{
index: 281474976710655,
secret: "7cc854b54e3e0dcdb010d7a3fee464a9687b" +
"e6e8db3be6854c475621e007a5dc",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659c1a" +
"8b4b5bec0c4b872abeba4cb8964",
successful: true,
},
{
index: 281474976710653,
secret: "2273e227a5b7449b6e70f1fb4652864038b1" +
"cbf9cd7c043a7d6456b7fc275ad8",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22ab21" +
"e9b506fd4998a51d54502e99116",
successful: true,
},
{
index: 281474976710651,
secret: "c65716add7aa98ba7acb236352d665cab173" +
"45fe45b55fb879ff80e6bd0c41dd",
successful: true,
},
{
index: 281474976710650,
secret: "969660042a28f32d9be17344e09374b37996" +
"2d03db1574df5a8a5a47e19ce3f2",
successful: true,
},
{
index: 281474976710649,
secret: "e7971de736e01da8ed58b94c2fc216cb1d" +
"ca9e326f3a96e7194fe8ea8af6c0a3",
successful: true,
},
{
index: 281474976710648,
secret: "05cde6323d949933f7f7b78776bcc1ea6d" +
"9b31447732e3802e1f7ac44b650e17",
successful: false,
},
},
},
{
name: "insert_secret #8 incorrect",
inserts: []testInsert{
{
index: 281474976710655,
secret: "7cc854b54e3e0dcdb010d7a3fee464a9687b" +
"e6e8db3be6854c475621e007a5dc",
successful: true,
},
{
index: 281474976710654,
secret: "c7518c8ae4660ed02894df8976fa1a3659c1a" +
"8b4b5bec0c4b872abeba4cb8964",
successful: true,
},
{
index: 281474976710653,
secret: "2273e227a5b7449b6e70f1fb4652864038b1" +
"cbf9cd7c043a7d6456b7fc275ad8",
successful: true,
},
{
index: 281474976710652,
secret: "27cddaa5624534cb6cb9d7da077cf2b22ab21" +
"e9b506fd4998a51d54502e99116",
successful: true,
},
{
index: 281474976710651,
secret: "c65716add7aa98ba7acb236352d665cab173" +
"45fe45b55fb879ff80e6bd0c41dd",
successful: true,
},
{
index: 281474976710650,
secret: "969660042a28f32d9be17344e09374b37996" +
"2d03db1574df5a8a5a47e19ce3f2",
successful: true,
},
{
index: 281474976710649,
secret: "a5a64476122ca0925fb344bdc1854c1c0a" +
"59fc614298e50a33e331980a220f32",
successful: true,
},
{
index: 281474976710648,
secret: "a7efbc61aac46d34f77778bac22c8a20c6" +
"a46ca460addc49009bda875ec88fa4",
successful: false,
},
},
},
}
// TestSpecificationShaChainInsert is used to check the consistence with
// specification hash insert function.
func TestSpecificationShaChainInsert(t *testing.T) {
for _, test := range tests {
receiver := NewRevocationStore()
for _, insert := range test.inserts {
secret, err := hashFromString(insert.secret)
if err != nil {
t.Fatal(err)
}
if err := receiver.Store(secret); err != nil {
if insert.successful {
t.Fatalf("Failed (%v): error was "+
"received but it shouldn't: "+
"%v", test.name, err)
}
} else {
if !insert.successful {
t.Fatalf("Failed (%v): error wasn't "+
"received", test.name)
}
}
}
t.Logf("Passed (%v)", test.name)
}
}
// TestShaChainStore checks the ability of shachain store to hold the produces
// secrets after recovering from bytes data.
func TestShaChainStore(t *testing.T) {
hash := chainhash.DoubleHashH([]byte("shachaintest"))
seed := &hash
sender := NewRevocationProducer(seed)
receiver := NewRevocationStore()
for n := uint64(0); n < 10000; n++ {
sha, err := sender.AtIndex(n)
if err != nil {
t.Fatal(err)
}
if err = receiver.Store(sha); err != nil {
t.Fatal(err)
}
}
data, err := receiver.ToBytes()
if err != nil {
t.Fatal(err)
}
receiver, err = NewRevocationStoreFromBytes(data)
if err != nil {
t.Fatal(err)
}
for n := uint64(0); n < 10000; n++ {
if _, err := receiver.LookUp(n); err != nil {
t.Fatal(err)
}
}
}

91
shachain/utils.go Normal file
View File

@ -0,0 +1,91 @@
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:
// 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.
func changeBit(hash []byte, position uint8) []byte {
byteNumber := position / 8
bitNumber := position % 8
hash[byteNumber] ^= (1 << bitNumber)
return hash
}
// 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 = 0
mask := (zero - 1) - uint64((1<<position)-1)
return (uint64(index) & mask)
}
// countTrailingZeros count number of of trailing zero bits, this function is
// used to determine the number of element bucket.
func countTrailingZeros(index index) uint8 {
var zeros uint8 = 0
for ; zeros < maxHeight; zeros++ {
if getBit(index, zeros) != 0 {
break
}
}
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.
func hashFromString(s string) (*chainhash.Hash, error) {
// Return 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
}