channeldb+lnwallet: replace elkrem with shachain

In this commit the initial implementation of revocation hash
generation 'elkrem' was replaced with 'shachain' Rusty Russel
implementation which currently enshrined in the spec. This alghoritm has
the same asymptotic characteristics but has more complex scheme
to determine wish hash we can drop and what needs to be stored
in order to be able to achive full compression.
This commit is contained in:
Andrey Samokhvalov 2016-12-14 17:01:48 +03:00 committed by Olaoluwa Osuntokun
parent b40afeaa08
commit f86557c3e4
11 changed files with 206 additions and 573 deletions

@ -9,9 +9,8 @@ import (
"time"
"github.com/boltdb/bolt"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
@ -89,9 +88,9 @@ var (
// and finally 2-of-2 multisig redeem script.
fundingTxnKey = []byte("fsk")
// elkremStateKey stores their current revocation hash, and our elkrem
// sender, and their elkrem receiver.
elkremStateKey = []byte("esk")
// preimageStateKey stores their current revocation hash, our
// preimage producer and their preimage store.
preimageStateKey = []byte("esk")
// deliveryScriptsKey stores the scripts for the final delivery in the
// case of a cooperative closure.
@ -225,8 +224,17 @@ type OpenChannel struct {
// aren't yet able to verify that it's actually in the hash chain.
TheirCurrentRevocation *btcec.PublicKey
TheirCurrentRevocationHash [32]byte
LocalElkrem *elkrem.ElkremSender
RemoteElkrem *elkrem.ElkremReceiver
// RevocationProducer is used to generate the revocation in such a way
// that remote side might store it efficiently and have the ability to
// restore the revocation by index if needed. Current implementation of
// secret producer is shachain producer.
RevocationProducer shachain.Producer
// RevocationStore is used to efficiently store the revocations for
// previous channels states sent to us by remote side. Current
// implementation of secret store is shachain store.
RevocationStore shachain.Store
// OurDeliveryScript is the script to be used to pay to us in
// cooperative closes.
@ -480,15 +488,16 @@ func (c *OpenChannel) AppendToRevocationLog(delta *ChannelDelta) error {
return err
}
// Persist the latest elkrem state to disk as the remote peer
// has just added to our local elkrem receiver, and given us a
// new pending revocation key.
if err := putChanElkremState(nodeChanBucket, c); err != nil {
// Persist the latest preimage state to disk as the remote peer
// has just added to our local preimage store, and
// given us a new pending revocation key.
if err := putChanPreimageState(nodeChanBucket, c); err != nil {
return err
}
// With the current elkrem state updated, append a new log
// entry recording this the delta of this state transition.
// With the current preimage producer/store state updated,
// append a new log entry recording this the delta of this state
// transition.
// TODO(roasbeef): could make the deltas relative, would save
// space, but then tradeoff for more disk-seeks to recover the
// full state.
@ -737,7 +746,7 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err := putChanFundingInfo(nodeChanBucket, channel); err != nil {
return err
}
if err := putChanElkremState(nodeChanBucket, channel); err != nil {
if err := putChanPreimageState(nodeChanBucket, channel); err != nil {
return err
}
if err := putChanDeliveryScripts(nodeChanBucket, channel); err != nil {
@ -774,8 +783,8 @@ func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err = fetchChanFundingInfo(nodeChanBucket, channel); err != nil {
return nil, fmt.Errorf("unable to read funding info: %v", err)
}
if err = fetchChanElkremState(nodeChanBucket, channel); err != nil {
return nil, fmt.Errorf("uable to read elkrem state: %v", err)
if err = fetchChanPreimageState(nodeChanBucket, channel); err != nil {
return nil, err
}
if err = fetchChanDeliveryScripts(nodeChanBucket, channel); err != nil {
return nil, fmt.Errorf("unable to read delivery scripts: %v", err)
@ -849,7 +858,7 @@ func deleteOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err := deleteChanFundingInfo(nodeChanBucket, channelID); err != nil {
return err
}
if err := deleteChanElkremState(nodeChanBucket, channelID); err != nil {
if err := deleteChanPreimageState(nodeChanBucket, channelID); err != nil {
return err
}
if err := deleteChanDeliveryScripts(nodeChanBucket, channelID); err != nil {
@ -1459,16 +1468,7 @@ func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) err
return nil
}
func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
var bc bytes.Buffer
if err := writeOutpoint(&bc, channel.ChanID); err != nil {
return err
}
elkremKey := make([]byte, len(elkremStateKey)+bc.Len())
copy(elkremKey[:3], elkremStateKey)
copy(elkremKey[3:], bc.Bytes())
func putChanPreimageState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
var b bytes.Buffer
revKey := channel.TheirCurrentRevocation.SerializeCompressed()
@ -1482,16 +1482,19 @@ func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error
// TODO(roasbeef): shouldn't be storing on disk, should re-derive as
// needed
senderBytes := channel.LocalElkrem.ToBytes()
if err := wire.WriteVarBytes(&b, 0, senderBytes); err != nil {
return err
}
reciverBytes, err := channel.RemoteElkrem.ToBytes()
data, err := channel.RevocationProducer.ToBytes()
if err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, reciverBytes); err != nil {
if err := wire.WriteVarBytes(&b, 0, data); err != nil {
return err
}
data, err = channel.RevocationStore.ToBytes()
if err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, data); err != nil {
return err
}
@ -1499,28 +1502,36 @@ func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error
return err
}
return nodeChanBucket.Put(elkremKey, b.Bytes())
var bc bytes.Buffer
if err := writeOutpoint(&bc, channel.ChanID); err != nil {
return err
}
preimageKey := make([]byte, len(preimageStateKey)+bc.Len())
copy(preimageKey[:3], preimageStateKey)
copy(preimageKey[3:], bc.Bytes())
return nodeChanBucket.Put(preimageKey, b.Bytes())
}
func deleteChanElkremState(nodeChanBucket *bolt.Bucket, chanID []byte) error {
elkremKey := make([]byte, len(elkremStateKey)+len(chanID))
copy(elkremKey[:3], elkremStateKey)
copy(elkremKey[3:], chanID)
return nodeChanBucket.Delete(elkremKey)
func deleteChanPreimageState(nodeChanBucket *bolt.Bucket, chanID []byte) error {
preimageKey := make([]byte, len(preimageStateKey)+len(chanID))
copy(preimageKey[:3], preimageStateKey)
copy(preimageKey[3:], chanID)
return nodeChanBucket.Delete(preimageKey)
}
func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
func fetchChanPreimageState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
var b bytes.Buffer
if err := writeOutpoint(&b, channel.ChanID); err != nil {
return err
}
elkremKey := make([]byte, len(elkremStateKey)+b.Len())
copy(elkremKey[:3], elkremStateKey)
copy(elkremKey[3:], b.Bytes())
preimageKey := make([]byte, len(preimageStateKey)+b.Len())
copy(preimageKey[:3], preimageStateKey)
copy(preimageKey[3:], b.Bytes())
elkremStateBytes := bytes.NewReader(nodeChanBucket.Get(elkremKey))
reader := bytes.NewReader(nodeChanBucket.Get(preimageKey))
revKeyBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "")
revKeyBytes, err := wire.ReadVarBytes(reader, 0, 1000, "")
if err != nil {
return err
}
@ -1529,32 +1540,30 @@ func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) err
return err
}
if _, err := elkremStateBytes.Read(channel.TheirCurrentRevocationHash[:]); err != nil {
if _, err := reader.Read(channel.TheirCurrentRevocationHash[:]); err != nil {
return err
}
// TODO(roasbeef): should be rederiving on fly, or encrypting on disk.
senderBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "")
producerBytes, err := wire.ReadVarBytes(reader, 0, 1000, "")
if err != nil {
return err
}
elkremRoot, err := chainhash.NewHash(senderBytes)
channel.RevocationProducer, err = shachain.NewRevocationProducerFromBytes(producerBytes)
if err != nil {
return err
}
channel.LocalElkrem = elkrem.NewElkremSender(*elkremRoot)
reciverBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "")
storeBytes, err := wire.ReadVarBytes(reader, 0, 1000, "")
if err != nil {
return err
}
remoteE, err := elkrem.ElkremReceiverFromBytes(reciverBytes)
channel.RevocationStore, err = shachain.NewRevocationStoreFromBytes(storeBytes)
if err != nil {
return err
}
channel.RemoteElkrem = remoteE
_, err = io.ReadFull(elkremStateBytes, channel.StateHintObsfucator[:])
_, err = io.ReadFull(reader, channel.StateHintObsfucator[:])
if err != nil {
return err
}

@ -9,7 +9,7 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/chaincfg/chainhash"
@ -117,17 +117,16 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
return nil, err
}
// Simulate 1000 channel updates via progression of the elkrem
// revocation trees.
sender := elkrem.NewElkremSender(key)
receiver := &elkrem.ElkremReceiver{}
// Simulate 1000 channel updates.
producer := shachain.NewRevocationProducer((*chainhash.Hash)(&key))
store := shachain.NewRevocationStore()
for i := 0; i < 1000; i++ {
preImage, err := sender.AtIndex(uint64(i))
preImage, err := producer.AtIndex(uint64(i))
if err != nil {
return nil, err
}
if receiver.AddNext(preImage); err != nil {
if store.Store(preImage); err != nil {
return nil, err
}
}
@ -150,8 +149,8 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
TheirBalance: btcutil.Amount(9000),
OurCommitTx: testTx,
OurCommitSig: bytes.Repeat([]byte{1}, 71),
LocalElkrem: sender,
RemoteElkrem: receiver,
RevocationProducer: producer,
RevocationStore: store,
StateHintObsfucator: obsfucator,
FundingOutpoint: testOutpoint,
OurMultiSigKey: privKey.PubKey(),
@ -207,34 +206,34 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
// The decoded channel state should be identical to what we stored
// above.
if !state.IdentityPub.IsEqual(newState.IdentityPub) {
t.Fatalf("their id doesn't match")
t.Fatal("their id doesn't match")
}
if !reflect.DeepEqual(state.ChanID, newState.ChanID) {
t.Fatalf("chan id's don't match")
t.Fatal("chan id's don't match")
}
if state.MinFeePerKb != newState.MinFeePerKb {
t.Fatalf("fee/kb doesn't match")
t.Fatal("fee/kb doesn't match")
}
if state.TheirDustLimit != newState.TheirDustLimit {
t.Fatalf("their dust limit doesn't match")
t.Fatal("their dust limit doesn't match")
}
if state.OurDustLimit != newState.OurDustLimit {
t.Fatalf("our dust limit doesn't match")
t.Fatal("our dust limit doesn't match")
}
if state.IsInitiator != newState.IsInitiator {
t.Fatalf("initiator status doesn't match")
t.Fatal("initiator status doesn't match")
}
if state.ChanType != newState.ChanType {
t.Fatalf("channel type doesn't match")
t.Fatal("channel type doesn't match")
}
if !bytes.Equal(state.OurCommitKey.SerializeCompressed(),
newState.OurCommitKey.SerializeCompressed()) {
t.Fatalf("our commit key doesn't match")
t.Fatal("our commit key doesn't match")
}
if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(),
newState.TheirCommitKey.SerializeCompressed()) {
t.Fatalf("their commit key doesn't match")
t.Fatal("their commit key doesn't match")
}
if state.Capacity != newState.Capacity {
@ -242,49 +241,49 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
newState.Capacity)
}
if state.OurBalance != newState.OurBalance {
t.Fatalf("our balance doesn't match")
t.Fatal("our balance doesn't match")
}
if state.TheirBalance != newState.TheirBalance {
t.Fatalf("their balance doesn't match")
t.Fatal("their balance doesn't match")
}
var b1, b2 bytes.Buffer
if err := state.OurCommitTx.Serialize(&b1); err != nil {
t.Fatalf("unable to serialize transaction")
t.Fatal("unable to serialize transaction")
}
if err := newState.OurCommitTx.Serialize(&b2); err != nil {
t.Fatalf("unable to serialize transaction")
t.Fatal("unable to serialize transaction")
}
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
t.Fatalf("ourCommitTx doesn't match")
t.Fatal("ourCommitTx doesn't match")
}
if !bytes.Equal(newState.OurCommitSig, state.OurCommitSig) {
t.Fatalf("commit sigs don't match")
t.Fatal("commit sigs don't match")
}
// TODO(roasbeef): replace with a single equal?
if !reflect.DeepEqual(state.FundingOutpoint, newState.FundingOutpoint) {
t.Fatalf("funding outpoint doesn't match")
t.Fatal("funding outpoint doesn't match")
}
if !bytes.Equal(state.OurMultiSigKey.SerializeCompressed(),
newState.OurMultiSigKey.SerializeCompressed()) {
t.Fatalf("our multisig key doesn't match")
t.Fatal("our multisig key doesn't match")
}
if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(),
newState.TheirMultiSigKey.SerializeCompressed()) {
t.Fatalf("their multisig key doesn't match")
t.Fatal("their multisig key doesn't match")
}
if !bytes.Equal(state.FundingWitnessScript, newState.FundingWitnessScript) {
t.Fatalf("redeem script doesn't match")
t.Fatal("redeem script doesn't match")
}
// The local and remote delivery scripts should be identical.
if !bytes.Equal(state.OurDeliveryScript, newState.OurDeliveryScript) {
t.Fatalf("our delivery address doesn't match")
t.Fatal("our delivery address doesn't match")
}
if !bytes.Equal(state.TheirDeliveryScript, newState.TheirDeliveryScript) {
t.Fatalf("their delivery address doesn't match")
t.Fatal("their delivery address doesn't match")
}
if state.NumUpdates != newState.NumUpdates {
@ -304,33 +303,45 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
state.TotalSatoshisSent, newState.TotalSatoshisSent)
}
if state.TotalSatoshisReceived != newState.TotalSatoshisReceived {
t.Fatalf("satoshis received doesn't match")
t.Fatal("satoshis received doesn't match")
}
if state.CreationTime.Unix() != newState.CreationTime.Unix() {
t.Fatalf("creation time doesn't match")
t.Fatal("creation time doesn't match")
}
// The local and remote elkrems should be identical.
if !bytes.Equal(state.LocalElkrem.ToBytes(), newState.LocalElkrem.ToBytes()) {
t.Fatalf("local elkrems don't match")
}
oldRemoteElkrem, err := state.RemoteElkrem.ToBytes()
// The local and remote producers should be identical.
oldProducer, err := state.RevocationProducer.ToBytes()
if err != nil {
t.Fatalf("unable to serialize old remote elkrem: %v", err)
t.Fatalf("can't convert old revocation producer to bytes: %v",
err)
}
newRemoteElkrem, err := newState.RemoteElkrem.ToBytes()
newProducer, err := newState.RevocationProducer.ToBytes()
if err != nil {
t.Fatalf("unable to serialize new remote elkrem: %v", err)
t.Fatalf("can't convert new revocation producer to bytes: %v",
err)
}
if !bytes.Equal(oldRemoteElkrem, newRemoteElkrem) {
t.Fatalf("remote elkrems don't match")
if !bytes.Equal(oldProducer, newProducer) {
t.Fatal("local producer don't match")
}
oldStore, err := state.RevocationStore.ToBytes()
if err != nil {
t.Fatalf("unable to serialize old remote store: %v", err)
}
newStore, err := newState.RevocationStore.ToBytes()
if err != nil {
t.Fatalf("unable to serialize new remote store: %v", err)
}
if !bytes.Equal(oldStore, newStore) {
t.Fatal("remote store don't match")
}
if !newState.TheirCurrentRevocation.IsEqual(state.TheirCurrentRevocation) {
t.Fatalf("revocation keys don't match")
t.Fatal("revocation keys don't match")
}
if !bytes.Equal(newState.TheirCurrentRevocationHash[:], state.TheirCurrentRevocationHash[:]) {
t.Fatalf("revocation hashes don't match")
t.Fatal("revocation hashes don't match")
}
if !reflect.DeepEqual(state.Htlcs[0], newState.Htlcs[0]) {
t.Fatalf("htlcs don't match: %v vs %v", spew.Sdump(state.Htlcs[0]),
@ -338,7 +349,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
}
if !bytes.Equal(state.StateHintObsfucator[:],
newState.StateHintObsfucator[:]) {
t.Fatalf("obsfuctators don't match")
t.Fatal("obsfuctators don't match")
}
// Finally to wrap up the test, delete the state of the channel within
@ -363,7 +374,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
// should yield no results.
openChans, err = cdb.FetchAllChannels()
if err != nil {
t.Fatalf("unable to fetch all open chans")
t.Fatal("unable to fetch all open chans")
}
if len(openChans) != 0 {
t.Fatalf("all channels not deleted, found %v", len(openChans))
@ -494,13 +505,13 @@ func TestChannelStateTransition(t *testing.T) {
// The two deltas (the original vs the on-disk version) should
// identical, and all HTLC data should properly be retained.
if delta.LocalBalance != diskDelta.LocalBalance {
t.Fatalf("local balances don't match")
t.Fatal("local balances don't match")
}
if delta.RemoteBalance != diskDelta.RemoteBalance {
t.Fatalf("remote balances don't match")
t.Fatal("remote balances don't match")
}
if delta.UpdateNum != diskDelta.UpdateNum {
t.Fatalf("update number doesn't match")
t.Fatal("update number doesn't match")
}
for i := 0; i < len(delta.Htlcs); i++ {
originalHTLC := delta.Htlcs[i]
@ -546,7 +557,7 @@ func TestChannelStateTransition(t *testing.T) {
}
if !bytes.Equal(updatedChannel[0].TheirCurrentRevocationHash[:],
newRevocation) {
t.Fatalf("revocation state wasn't synced!")
t.Fatal("revocation state wasn't synced!")
}
// Now attempt to delete the channel from the database.
@ -569,6 +580,6 @@ func TestChannelStateTransition(t *testing.T) {
// revocation log has been deleted.
_, err = updatedChannel[0].FindPreviousState(uint64(delta.UpdateNum))
if err == nil {
t.Fatalf("revocation log search should've failed")
t.Fatal("revocation log search should've failed")
}
}

@ -1,151 +0,0 @@
package elkrem
import (
"fmt"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
/* elkrem is a simpler alternative to the 64 dimensional sha-chain.
it's basically a reverse merkle tree. If we want to provide 2**64 possible
hashes, this requires a worst case computation of 63 hashes for the
sender, and worst-case storage of 64 hashes for the receiver.
The operations are left hash L() and right hash R(), which are
hash(parent) and hash(parent, 1) respectively. (concatenate one byte)
Here is a shorter example of a tree with 8 leaves and 15 total nodes.
The sender first computes the bottom left leaf 0b0000. This is
L(L(L(L(root)))). The receiver stores leaf 0.
Next the sender computes 0b0001. R(L(L(L(root)))). Receiver stores.
Next sender computes 0b1000 (8). L(L(L(root))). Receiver stores this, and
discards leaves 0b0000 and 0b0001, as they have the parent node 8.
For total hashes (2**h)-1 requires a tree of height h.
Sender:
as state, must store 1 hash (root) and that's all
generate any index, compute at most h hashes.
Receiver:
as state, must store at most h+1 hashes and the index of each hash (h*(h+1)) bits
to compute a previous index, compute at most h hashes.
*/
const maxIndex = uint64(281474976710654) // 2^48 - 2
const maxHeight = uint8(47)
// You can calculate h from i but I can't figure out how without taking
// O(i) ops. Feels like there should be a clever O(h) way. 1 byte, whatever.
type ElkremNode struct {
h uint8 // height of this node
i uint64 // index (i'th node)
sha *chainhash.Hash // hash
}
type ElkremSender struct {
root *chainhash.Hash // root hash of the tree
}
type ElkremReceiver struct {
s []ElkremNode // store of received hashes
}
func LeftSha(in chainhash.Hash) chainhash.Hash {
return chainhash.DoubleHashH(in[:]) // left is sha(sha(in))
}
func RightSha(in chainhash.Hash) chainhash.Hash {
return chainhash.DoubleHashH(append(in[:], 0x01)) // sha(sha(in, 1))
}
// iterative descent of sub-tree. w = hash number you want. i = input index
// h = height of input index. sha = input hash
func descend(w, i uint64, h uint8, sha chainhash.Hash) (chainhash.Hash, error) {
for w < i {
if w <= i-(1<<h) { // left
sha = LeftSha(sha)
i = i - (1 << h) // left descent reduces index by 2**h
} else { // right
sha = RightSha(sha)
i-- // right descent reduces index by 1
}
if h == 0 { // avoid underflowing h
break
}
h-- // either descent reduces height by 1
}
if w != i { // somehow couldn't / didn't end up where we wanted to go
return sha, fmt.Errorf("can't generate index %d from %d", w, i)
}
return sha, nil
}
// Creates an Elkrem Sender from a root hash.
func NewElkremSender(r chainhash.Hash) *ElkremSender {
var e ElkremSender
e.root = &r
return &e
}
// AtIndex skips to the requested index
// should never error; remove error..?
func (e *ElkremSender) AtIndex(w uint64) (*chainhash.Hash, error) {
out, err := descend(w, maxIndex, maxHeight, *e.root)
return &out, err
}
// AddNext inserts the next hash in the tree. Returns an error if
// the incoming hash doesn't fit.
func (e *ElkremReceiver) AddNext(sha *chainhash.Hash) error {
// note: careful about atomicity / disk writes here
var n ElkremNode
n.sha = sha
t := len(e.s) - 1 // top of stack
if t >= 0 { // if this is not the first hash (>= because we -1'd)
n.i = e.s[t].i + 1 // incoming index is tip of stack index + 1
}
if t > 0 && e.s[t-1].h == e.s[t].h { // top 2 elements are equal height
// next node must be parent; verify and remove children
n.h = e.s[t].h + 1 // assign height
l := LeftSha(*sha) // calc l child
r := RightSha(*sha) // calc r child
if !e.s[t-1].sha.IsEqual(&l) { // test l child
return fmt.Errorf("left child doesn't match, expect %s got %s",
e.s[t-1].sha.String(), l.String())
}
if !e.s[t].sha.IsEqual(&r) { // test r child
return fmt.Errorf("right child doesn't match, expect %s got %s",
e.s[t].sha.String(), r.String())
}
e.s = e.s[:len(e.s)-2] // l and r children OK, remove them
} // if that didn't happen, height defaults to 0
e.s = append(e.s, n) // append new node to stack
return nil
}
// AtIndex returns the w'th hash in the receiver.
func (e *ElkremReceiver) AtIndex(w uint64) (*chainhash.Hash, error) {
if e == nil || e.s == nil {
return nil, fmt.Errorf("nil elkrem receiver")
}
var out ElkremNode // node we will eventually return
for _, n := range e.s { // go through stack
if w <= n.i { // found one bigger than or equal to what we want
out = n
break
}
}
if out.sha == nil { // didn't find anything
return nil, fmt.Errorf("receiver has max %d, less than requested %d",
e.s[len(e.s)-1].i, w)
}
sha, err := descend(w, out.i, out.h, *out.sha)
return &sha, err
}
// UpTo tells you what the receiver can go up to.
func (e *ElkremReceiver) UpTo() uint64 {
if len(e.s) < 1 {
return 0
}
return e.s[len(e.s)-1].i
}

@ -1,57 +0,0 @@
package elkrem
import (
"testing"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
// TestElkremBig tries 10K hashes
func TestElkremBig(t *testing.T) {
var rcv ElkremReceiver
sndr := NewElkremSender(chainhash.DoubleHashH([]byte("elktest")))
for n := uint64(0); n < 10000; n++ {
sha, err := sndr.AtIndex(n)
if err != nil {
t.Fatal(err)
}
if err = rcv.AddNext(sha); err != nil {
t.Fatal(err)
}
}
ReceiverSerdesTest(t, &rcv)
for n := uint64(0); n < 10000; n += 500 {
if _, err := rcv.AtIndex(n); err != nil {
t.Fatal(err)
}
}
}
// TestElkremLess tries 10K hashes
func TestElkremLess(t *testing.T) {
var rcv ElkremReceiver
sndr := NewElkremSender(chainhash.DoubleHashH([]byte("elktest2")))
for n := uint64(0); n < 5000; n++ {
sha, err := sndr.AtIndex(n)
if err != nil {
t.Fatal(err)
}
if err = rcv.AddNext(sha); err != nil {
t.Fatal(err)
}
}
for n := uint64(0); n < 5000; n += 500 {
if _, err := rcv.AtIndex(n); err != nil {
t.Fatal(err)
}
}
}

@ -1,130 +0,0 @@
package elkrem
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/roasbeef/btcd/chaincfg/chainhash"
)
/* Serialization and Deserialization methods for the Elkrem structs.
Senders turn into 41 byte long slices. Receivers are variable length,
with 41 bytes for each stored hash, up to a maximum of 48. Receivers are
prepended with the total number of hashes, so the total max size is 1969 bytes.
*/
// ToBytes turns the Elkrem Receiver into a bunch of bytes in a slice.
// first the number of nodes (1 byte), then a series of 41 byte long
// serialized nodes, which are 1 byte height, 8 byte index, 32 byte hash.
func (e *ElkremReceiver) ToBytes() ([]byte, error) {
numOfNodes := uint8(len(e.s))
// 0 element receiver also OK. Just an empty slice.
if numOfNodes == 0 {
return nil, nil
}
if numOfNodes > maxHeight+1 {
return nil, fmt.Errorf("Broken ElkremReceiver has %d nodes, max 64",
len(e.s))
}
var buf bytes.Buffer // create buffer
// write number of nodes (1 byte)
err := binary.Write(&buf, binary.BigEndian, numOfNodes)
if err != nil {
return nil, err
}
for _, node := range e.s {
// write 1 byte height
err = binary.Write(&buf, binary.BigEndian, node.h)
if err != nil {
return nil, err
}
// write 8 byte index
err = binary.Write(&buf, binary.BigEndian, node.i)
if err != nil {
return nil, err
}
if node.sha == nil {
return nil, fmt.Errorf("node %d has nil hash", node.i)
}
// write 32 byte sha hash
n, err := buf.Write(node.sha[:])
if err != nil {
return nil, err
}
if n != 32 { // make sure that was 32 bytes
return nil, fmt.Errorf("%d byte hash, expect 32", n)
}
}
if buf.Len() != (int(numOfNodes)*41)+1 {
return nil, fmt.Errorf("Somehow made wrong size buf, got %d expect %d",
buf.Len(), (numOfNodes*41)+1)
}
return buf.Bytes(), nil
}
func ElkremReceiverFromBytes(b []byte) (*ElkremReceiver, error) {
var e ElkremReceiver
if len(b) == 0 { // empty receiver, which is OK
return &e, nil
}
buf := bytes.NewBuffer(b)
// read 1 byte number of nodes stored in receiver
numOfNodes, err := buf.ReadByte()
if err != nil {
return nil, err
}
if numOfNodes < 1 || numOfNodes > maxHeight+1 {
return nil, fmt.Errorf("Read invalid number of nodes: %d", numOfNodes)
}
if buf.Len() != (int(numOfNodes) * 41) {
return nil, fmt.Errorf("Remaining buf wrong size, expect %d got %d",
(numOfNodes * 41), buf.Len())
}
e.s = make([]ElkremNode, numOfNodes)
for j, _ := range e.s {
e.s[j].sha = new(chainhash.Hash)
// read 1 byte height
err := binary.Read(buf, binary.BigEndian, &e.s[j].h)
if err != nil {
return nil, err
}
// read 8 byte index
err = binary.Read(buf, binary.BigEndian, &e.s[j].i)
if err != nil {
return nil, err
}
// read 32 byte sha hash
err = e.s[j].sha.SetBytes(buf.Next(32))
if err != nil {
return nil, err
}
// sanity check. Note that this doesn't check that index and height
// match. Could add that but it's slow.
if e.s[j].h > maxHeight { // check for super high nodes
return nil, fmt.Errorf("Read invalid node height %d", e.s[j].h)
}
if e.s[j].i > maxIndex { // check for index higher than height allows
return nil, fmt.Errorf("Node claims index %d; %d max at height %d",
e.s[j].i, maxIndex, e.s[j].h)
}
if j > 0 { // check that node heights are descending
if e.s[j-1].h < e.s[j].h {
return nil, fmt.Errorf("Node heights out of order")
}
}
}
return &e, nil
}
// ToBytes returns the root of the elkrem sender tree as a byte slice. This
// function is in place to allow one to export the root of the tree. However,
// node that if one uses a deterministic procedure to generate the root, then
// serialization isn't necessary as it can simply be re-derived on the fly.
func (e *ElkremSender) ToBytes() []byte {
return e.root[:]
}

@ -1,49 +0,0 @@
package elkrem
import (
"bytes"
"testing"
)
func ReceiverSerdesTest(t *testing.T, rcv *ElkremReceiver) {
b, err := rcv.ToBytes()
if err != nil {
t.Fatal(err)
}
rcv2, err := ElkremReceiverFromBytes(b)
if err != nil {
t.Fatal(err)
}
b2, err := rcv2.ToBytes()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, b2) {
t.Fatalf("First and second serializations different")
}
}
//func SenderSerdesTest(t *testing.T, sndr *ElkremSender) {
// b, err := sndr.ToBytes()
// if err != nil {
// t.Fatal(err)
// }
// t.Logf("Serialized sender; %d bytes, hex:\n%x\n", len(b), b)
// *sndr, err = ElkremSenderFromBytes(b)
// if err != nil {
// t.Fatal(err)
// }
// b2, err := sndr.ToBytes()
// if err != nil {
// t.Fatal(err)
// }
// if !bytes.Equal(b, b2) {
// t.Fatalf("First and second serializations different")
// }
//}

@ -734,10 +734,10 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
return nil, err
}
// With the state number broadcast known, we can now derive the proper
// leaf from our revocation tree necessary to sweep the remote party's
// With the state number broadcast known, we can now derive/restore the
// proper revocation preimage necessary to sweep the remote party's
// output.
revocationPreimage, err := chanState.RemoteElkrem.AtIndex(stateNum)
revocationPreimage, err := chanState.RevocationStore.LookUp(stateNum)
if err != nil {
return nil, err
}
@ -1482,7 +1482,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(rawSig []byte) error {
// derive the key+hash needed to construct the new commitment view and
// state.
nextHeight := lc.currentHeight + 1
revocation, err := lc.channelState.LocalElkrem.AtIndex(nextHeight)
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
if err != nil {
return err
}
@ -1577,7 +1577,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, err
// Now that we've accept a new state transition, we send the remote
// party the revocation for our current commitment state.
revocationMsg := &lnwire.RevokeAndAck{}
currentRevocation, err := lc.channelState.LocalElkrem.AtIndex(lc.currentHeight)
currentRevocation, err := lc.channelState.RevocationProducer.AtIndex(lc.currentHeight)
if err != nil {
return nil, err
}
@ -1586,7 +1586,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, err
// Along with this revocation, we'll also send an additional extension
// to our revocation window to the remote party.
lc.revocationWindowEdge++
revocationEdge, err := lc.channelState.LocalElkrem.AtIndex(lc.revocationWindowEdge)
revocationEdge, err := lc.channelState.RevocationProducer.AtIndex(lc.revocationWindowEdge)
if err != nil {
return nil, err
}
@ -1655,11 +1655,10 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ([]*P
currentRevocationKey := lc.channelState.TheirCurrentRevocation
pendingRevocation := chainhash.Hash(revMsg.Revocation)
// Ensure the new preimage fits in properly within the elkrem receiver
// tree. If this fails, then all other checks are skipped.
// Ensure that the new pre-image can be placed in preimage store.
// TODO(rosbeef): abstract into func
remoteElkrem := lc.channelState.RemoteElkrem
if err := remoteElkrem.AddNext(&pendingRevocation); err != nil {
store := lc.channelState.RevocationStore
if err := store.Store(&pendingRevocation); err != nil {
return nil, err
}
@ -1698,8 +1697,8 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ([]*P
// At this point, the revocation has been accepted, and we've rotated
// the current revocation key+hash for the remote party. Therefore we
// sync now to ensure the elkrem receiver state is consistent with the
// current commitment height.
// sync now to ensure the revocation producer state is consistent with
// the current commitment height.
tail := lc.remoteCommitChain.tail()
delta, err := tail.toChannelDelta()
if err != nil {
@ -1778,7 +1777,7 @@ func (lc *LightningChannel) ExtendRevocationWindow() (*lnwire.RevokeAndAck, erro
revMsg.ChannelPoint = *lc.channelState.ChanID
nextHeight := lc.revocationWindowEdge + 1
revocation, err := lc.channelState.LocalElkrem.AtIndex(nextHeight)
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
if err != nil {
return nil, err
}
@ -2147,8 +2146,8 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
// Re-derive the original pkScript for out to-self output within the
// commitment transaction. We'll need this for the created sign
// descriptor.
elkrem := lc.channelState.LocalElkrem
unusedRevocation, err := elkrem.AtIndex(lc.currentHeight)
producer := lc.channelState.RevocationProducer
unusedRevocation, err := producer.AtIndex(lc.currentHeight)
if err != nil {
return nil, err
}

@ -11,8 +11,8 @@ import (
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/blockchain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
@ -202,15 +202,17 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, bobKeyPub, aliceKeyPub))
bobFirstRevoke, err := bobElkrem.AtIndex(0)
bobRoot := deriveRevocationRoot(bobKeyPriv, bobKeyPub, aliceKeyPub)
bobPreimageProducer := shachain.NewRevocationProducer(bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
bobRevokeKey := DeriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub))
aliceFirstRevoke, err := aliceElkrem.AtIndex(0)
aliceRoot := deriveRevocationRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub)
alicePreimageProducer := shachain.NewRevocationProducer(aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
@ -262,8 +264,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
LocalCsvDelay: csvTimeoutAlice,
RemoteCsvDelay: csvTimeoutBob,
TheirCurrentRevocation: bobRevokeKey,
LocalElkrem: aliceElkrem,
RemoteElkrem: &elkrem.ElkremReceiver{},
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
TheirDustLimit: bobDustLimit,
OurDustLimit: aliceDustLimit,
Db: dbAlice,
@ -288,8 +290,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
LocalCsvDelay: csvTimeoutBob,
RemoteCsvDelay: csvTimeoutAlice,
TheirCurrentRevocation: aliceRevokeKey,
LocalElkrem: bobElkrem,
RemoteElkrem: &elkrem.ElkremReceiver{},
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
TheirDustLimit: aliceDustLimit,
OurDustLimit: bobDustLimit,
Db: dbBob,
@ -598,9 +600,10 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
// transaction with some degree of error corresponds to the actual size.
func TestCheckCommitTxSize(t *testing.T) {
checkSize := func(channel *LightningChannel, count int) {
// Due to variable size of the signatures (71-73) we may have
// an estimation error.
BaseCommitmentTxSizeEstimationError := 4
// Due to variable size of the signatures (70-73) in
// witness script actual size of commitment transaction might
// be lower on 6 weight.
BaseCommitmentTxSizeEstimationError := 6
commitTx, err := channel.getSignedCommitTx()
if err != nil {

@ -752,31 +752,31 @@ func DeriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
return privRevoke
}
// deriveElkremRoot derives an elkrem root unique to a channel given the
// deriveRevocationRoot derives an root unique to a channel given the
// private key for our public key in the 2-of-2 multi-sig, and the remote
// node's multi-sig public key. The root is derived using the HKDF[1][2]
// node's multi-sig public key. The seed is derived using the HKDF[1][2]
// instantiated with sha-256. The secret data used is our multi-sig private
// key, with the salt being the remote node's public key.
//
// [1]: https://eprint.iacr.org/2010/264.pdf
// [2]: https://tools.ietf.org/html/rfc5869
func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
func deriveRevocationRoot(derivationRoot *btcec.PrivateKey,
localMultiSigKey *btcec.PublicKey,
remoteMultiSigKey *btcec.PublicKey) chainhash.Hash {
remoteMultiSigKey *btcec.PublicKey) *chainhash.Hash {
secret := elkremDerivationRoot.Serialize()
secret := derivationRoot.Serialize()
salt := localMultiSigKey.SerializeCompressed()
info := remoteMultiSigKey.SerializeCompressed()
rootReader := hkdf.New(sha256.New, secret, salt, info)
seedReader := hkdf.New(sha256.New, secret, salt, info)
// It's safe to ignore the error her as we know for sure that we won't
// be draining the HKDF past its available entropy horizon.
// TODO(roasbeef): revisit...
var elkremRoot chainhash.Hash
rootReader.Read(elkremRoot[:])
var root chainhash.Hash
seedReader.Read(root[:])
return elkremRoot
return &root
}
// SetStateNumHint encodes the current state number within the passed

@ -85,14 +85,14 @@ const (
// - Marker: 1 byte
WitnessHeaderSize = 1 + 1
// CommitmentTransaction: 125 bytes
// CommitmentTransaction: 125 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn:
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 1 byte
// - TxOut:
// - TxOut: 74 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// ....HTLCOutputs...

@ -11,10 +11,10 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcutil/hdkeychain"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
@ -27,9 +27,9 @@ const (
// outside word.
msgBufferSize = 100
// elkremRootIndex is the top level HD key index from which secrets
// used to generate elkrem roots should be derived from.
elkremRootIndex = hdkeychain.HardenedKeyStart + 1
// revocationRootIndex is the top level HD key index from which secrets
// used to generate producer roots should be derived from.
revocationRootIndex = hdkeychain.HardenedKeyStart + 1
// identityKeyIndex is the top level HD key index which is used to
// generate/rotate identity keys.
@ -42,7 +42,6 @@ const (
)
var (
// Namespace bucket keys.
lightningNamespaceKey = []byte("ln-wallet")
waddrmgrNamespaceKey = []byte("waddrmgr")
@ -757,11 +756,11 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it
// to the chain).
e := &elkrem.ElkremReceiver{}
pendingReservation.partialState.RemoteElkrem = e
s := shachain.NewRevocationStore()
pendingReservation.partialState.RevocationStore = s
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
masterElkremRoot, err := l.deriveMasterElkremRoot()
masterElkremRoot, err := l.deriveMasterRevocationRoot()
if err != nil {
req.err <- err
return
@ -769,12 +768,11 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Now that we have their commitment key, we can create the revocation
// key for the first version of our commitment transaction. To do so,
// we'll first create our elkrem root, then grab the first pre-iamge
// from it.
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
elkremSender := elkrem.NewElkremSender(elkremRoot)
pendingReservation.partialState.LocalElkrem = elkremSender
firstPreimage, err := elkremSender.AtIndex(0)
// we'll first create our root, then produce the first pre-image.
root := deriveRevocationRoot(masterElkremRoot, ourKey, theirKey)
producer := shachain.NewRevocationProducer(root)
pendingReservation.partialState.RevocationProducer = producer
firstPreimage, err := producer.AtIndex(0)
if err != nil {
req.err <- err
return
@ -812,7 +810,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// TODO(roasbeef): define obsfucator scheme for dual funder
var stateObsfucator [StateHintSize]byte
if pendingReservation.partialState.IsInitiator {
stateObsfucator, err = deriveStateHintObsfucator(elkremSender)
stateObsfucator, err = deriveStateHintObfuscator(producer)
if err != nil {
req.err <- err
return
@ -902,7 +900,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
}
pendingReservation.partialState.FundingWitnessScript = witnessScript
masterElkremRoot, err := l.deriveMasterElkremRoot()
masterElkremRoot, err := l.deriveMasterRevocationRoot()
if err != nil {
req.err <- err
return
@ -910,22 +908,22 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
// Now that we know their commitment key, we can create the revocation
// key for our version of the initial commitment transaction.
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
elkremSender := elkrem.NewElkremSender(elkremRoot)
firstPreimage, err := elkremSender.AtIndex(0)
root := deriveRevocationRoot(masterElkremRoot, ourKey, theirKey)
producer := shachain.NewRevocationProducer(root)
firstPreimage, err := producer.AtIndex(0)
if err != nil {
req.err <- err
return
}
pendingReservation.partialState.LocalElkrem = elkremSender
pendingReservation.partialState.RevocationProducer = producer
theirCommitKey := theirContribution.CommitKey
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it
// to the chain).
remoteElkrem := &elkrem.ElkremReceiver{}
pendingReservation.partialState.RemoteElkrem = remoteElkrem
remotePreimageStore := shachain.NewRevocationStore()
pendingReservation.partialState.RevocationStore = remotePreimageStore
// Record the counterpaty's remaining contributions to the channel,
// converting their delivery address into a public key script.
@ -1379,11 +1377,11 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amoun
return nil
}
// deriveMasterElkremRoot derives the private key which serves as the master
// elkrem root. This master secret is used as the secret input to a HKDF to
// generate elkrem secrets based on random, but public data.
func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
masterElkremRoot, err := l.rootKey.Child(elkremRootIndex)
// deriveMasterRevocationRoot derives the private key which serves as the master
// producer root. This master secret is used as the secret input to a HKDF to
// generate revocation secrets based on random, but public data.
func (l *LightningWallet) deriveMasterRevocationRoot() (*btcec.PrivateKey, error) {
masterElkremRoot, err := l.rootKey.Child(revocationRootIndex)
if err != nil {
return nil, err
}
@ -1391,34 +1389,34 @@ func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
return masterElkremRoot.ECPrivKey()
}
// deriveStateHintObsfucator derives the bytes to be used for obsfucatating the
// state hints from the elkerem root to be used for a new channel. The
// obsfucator is generated by performing an additional sha256 hash of the first
// child derived from the elkrem root. The leading 4 bytes are used for the
// obsfucator.
func deriveStateHintObsfucator(elkremRoot *elkrem.ElkremSender) ([StateHintSize]byte, error) {
var obsfucator [StateHintSize]byte
// deriveStateHintObfuscator derives the bytes to be used for obfuscating the
// state hints from the root to be used for a new channel. The
// obfuscator is generated by performing an additional sha256 hash of the first
// child derived from the revocation root. The leading 4 bytes are used for the
// obfuscator.
func deriveStateHintObfuscator(producer shachain.Producer) ([StateHintSize]byte, error) {
var obfuscator [StateHintSize]byte
firstChild, err := elkremRoot.AtIndex(0)
firstChild, err := producer.AtIndex(0)
if err != nil {
return obsfucator, err
return obfuscator, err
}
grandChild := fastsha256.Sum256(firstChild[:])
copy(obsfucator[:], grandChild[:])
copy(obfuscator[:], grandChild[:])
return obsfucator, nil
return obfuscator, nil
}
// initStateHints properly sets the obsfucated state hints on both commitment
// transactions using the passed obsfucator.
func initStateHints(commit1, commit2 *wire.MsgTx,
obsfucator [StateHintSize]byte) error {
obfuscator [StateHintSize]byte) error {
if err := SetStateNumHint(commit1, 0, obsfucator); err != nil {
if err := SetStateNumHint(commit1, 0, obfuscator); err != nil {
return err
}
if err := SetStateNumHint(commit2, 0, obsfucator); err != nil {
if err := SetStateNumHint(commit2, 0, obfuscator); err != nil {
return err
}
@ -1426,7 +1424,7 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
}
// selectInputs selects a slice of inputs necessary to meet the specified
// selection amount. If input selection is unable to suceed to to insuffcient
// selection amount. If input selection is unable to succeed to to insufficient
// funds, a non-nil error is returned. Additionally, the total amount of the
// selected coins are returned in order for the caller to properly handle
// change+fees.