shachain: complete shachain implementation
This commit is contained in:
parent
3a647869b6
commit
b40afeaa08
165
shachain/element.go
Normal file
165
shachain/element.go
Normal 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
255
shachain/element_test.go
Normal 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
76
shachain/producer.go
Normal 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
40
shachain/producer_test.go
Normal 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())
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -1 +0,0 @@
|
||||
package shachain
|
186
shachain/store.go
Normal file
186
shachain/store.go
Normal 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
466
shachain/store_test.go
Normal 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
91
shachain/utils.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user