diff --git a/shachain/element.go b/shachain/element.go new file mode 100644 index 00000000..394ae45d --- /dev/null +++ b/shachain/element.go @@ -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 +} diff --git a/shachain/element_test.go b/shachain/element_test.go new file mode 100644 index 00000000..5588a758 --- /dev/null +++ b/shachain/element_test.go @@ -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) + } +} diff --git a/shachain/producer.go b/shachain/producer.go new file mode 100644 index 00000000..0c887805 --- /dev/null +++ b/shachain/producer.go @@ -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 +} diff --git a/shachain/producer_test.go b/shachain/producer_test.go new file mode 100644 index 00000000..42132afe --- /dev/null +++ b/shachain/producer_test.go @@ -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()) + } +} diff --git a/shachain/shachain.go b/shachain/shachain.go deleted file mode 100644 index 956c3fd1..00000000 --- a/shachain/shachain.go +++ /dev/null @@ -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 -} diff --git a/shachain/shachain_test.go b/shachain/shachain_test.go deleted file mode 100644 index 531b9204..00000000 --- a/shachain/shachain_test.go +++ /dev/null @@ -1 +0,0 @@ -package shachain diff --git a/shachain/store.go b/shachain/store.go new file mode 100644 index 00000000..6f434ca3 --- /dev/null +++ b/shachain/store.go @@ -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 +} diff --git a/shachain/store_test.go b/shachain/store_test.go new file mode 100644 index 00000000..c97f388d --- /dev/null +++ b/shachain/store_test.go @@ -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) + } + } +} diff --git a/shachain/utils.go b/shachain/utils.go new file mode 100644 index 00000000..0b3c4e0b --- /dev/null +++ b/shachain/utils.go @@ -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< 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 +}