Skip to content
Merged
2 changes: 1 addition & 1 deletion channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ type OpenChannel struct {
// StateHintObsfucator are the btyes selected by the initiator (derived
// from their shachain root) to obsfucate the state-hint encoded within
// the commitment transaction.
StateHintObsfucator [4]byte
StateHintObsfucator [6]byte

// ChanType denotes which type of channel this is.
ChanType ChannelType
Expand Down
2 changes: 1 addition & 1 deletion channeldb/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
}
}

var obsfucator [4]byte
var obsfucator [6]byte
copy(obsfucator[:], key[:])

return &OpenChannel{
Expand Down
4 changes: 2 additions & 2 deletions lnwallet/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
// Decode the state hint encoded within the commitment transaction to
// determine if this is a revoked state or not.
obsfucator := lc.channelState.StateHintObsfucator
broadcastStateNum := uint64(GetStateNumHint(commitTxBroadcast, obsfucator))
broadcastStateNum := GetStateNumHint(commitTxBroadcast, obsfucator)

currentStateNum := lc.currentHeight

Expand Down Expand Up @@ -1125,7 +1125,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
// quickly recovering the necessary penalty state in the case of an
// uncooperative broadcast.
obsfucator := lc.channelState.StateHintObsfucator
stateNum := uint32(nextHeight)
stateNum := nextHeight
if err := SetStateNumHint(commitTx, stateNum, obsfucator); err != nil {
return nil, err
}
Expand Down
60 changes: 39 additions & 21 deletions lnwallet/script_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,28 @@ var (
SequenceLockTimeSeconds = uint32(1 << 22)
SequenceLockTimeMask = uint32(0x0000ffff)
OP_CHECKSEQUENCEVERIFY byte = txscript.OP_NOP3

// TimelockShift is used to make sure the commitment transaction is
// spendable by setting the locktime with it so that it is larger than
// 500,000,000, thus interpreting it as Unix epoch timestamp and not
// a block height. It is also smaller than the current timestamp which
// has bit (1 << 30) set, so there is no risk of having the commitment
// transaction be rejected. This way we can safely use the lower 24 bits
// of the locktime field for part of the obscured commitment transaction
// number.
TimelockShift = uint32(1 << 29)
)

const (
// StateHintSize is the total number of bytes used between the sequence
// number and locktime of the commitment transaction use to encode a hint
// to the state number of a particular commitment transaction.
StateHintSize = 4
StateHintSize = 6

// maxStateHint is the maximum state number we're able to encode using
// StateHintSize bytes amongst the sequence number and locktime fields
// of the commitment transaction.
maxStateHint = (1 << 31) - 1
maxStateHint = (1 << 48) - 1
)

// witnessScriptHash generates a pay-to-witness-script-hash public key script
Expand Down Expand Up @@ -770,20 +780,21 @@ func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
}

// SetStateNumHint encodes the current state number within the passed
// commitment transaction by re-purposing the sequence fields in the input of
// the commitment transaction to encode the obfuscated state number. The state
// number is encoded using 31-bits of the sequence number, with the top bit set
// in order to disable BIP0068 (sequence locks) semantics. Finally before
// encoding, the obfuscater is XOR'd against the state number in order to hide
// the exact state number from the PoV of outside parties.
// commitment transaction by re-purposing the locktime and sequence fields
// in the commitment transaction to encode the obfuscated state number.
// The state number is encoded using 48 bits. The lower 24 bits of the
// locktime are the lower 24 bits of the obfuscated state number and the
// lower 24 bits of the sequence field are the higher 24 bits. Finally
// before encoding, the obfuscater is XOR'd against the state number in
// order to hide the exact state number from the PoV of outside parties.
// TODO(roasbeef): unexport function after bobNode is gone
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint32,
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint64,
obsfucator [StateHintSize]byte) error {

// With the current schema we are only able able to encode state num
// hints up to 2^31. Therefore if the passed height is greater than our
// hints up to 2^48. Therefore if the passed height is greater than our
// state hint ceiling, then exit early.
if stateNum >= maxStateHint {
if stateNum > maxStateHint {
return fmt.Errorf("unable to encode state, %v is greater "+
"state num that max of %v", stateNum, maxStateHint)
}
Expand All @@ -793,16 +804,20 @@ func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint32,
"instead has %v", len(commitTx.TxIn))
}

// Convert the obfuscater into a uint32, then XOR that against the
// Convert the obfuscator into a uint64, then XOR that against the
// targeted height in order to obfuscate the state number of the
// commitment transaction in the case that either commitment
// transaction is broadcast directly on chain.
xorInt := binary.BigEndian.Uint32(obsfucator[:]) & (^wire.SequenceLockTimeDisabled)
var obfs [8]byte
copy(obfs[2:], obsfucator[:])
xorInt := binary.BigEndian.Uint64(obfs[:])

stateNum = stateNum ^ xorInt

// Set the height bit of the sequence number in order to disable any
// sequence locks semantics.
commitTx.TxIn[0].Sequence = stateNum | wire.SequenceLockTimeDisabled
commitTx.TxIn[0].Sequence = uint32(stateNum>>24) | wire.SequenceLockTimeDisabled
commitTx.LockTime = uint32(stateNum&0xFFFFFF) | TimelockShift

return nil
}
Expand All @@ -813,14 +828,17 @@ func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint32,
//
// See setStateNumHint for further details w.r.t exactly how the state-hints
// are encoded.
func GetStateNumHint(commitTx *wire.MsgTx, obsfucator [StateHintSize]byte) uint32 {
// Convert the obfuscater into a uint32, this will be used to
func GetStateNumHint(commitTx *wire.MsgTx, obsfucator [StateHintSize]byte) uint64 {
// Convert the obfuscater into a uint64, this will be used to
// de-obfuscate the final recovered state number.
xorInt := binary.BigEndian.Uint32(obsfucator[:]) & (^wire.SequenceLockTimeDisabled)

// Retrieve the sole state hint from the sequence number of the
// transaction. In the process un-set the top bit.
stateNumXor := commitTx.TxIn[0].Sequence & (^wire.SequenceLockTimeDisabled)
var obfs [8]byte
copy(obfs[2:], obsfucator[:])
xorInt := binary.BigEndian.Uint64(obfs[:])

// Retrieve the state hint from the sequence number and locktime
// of the transaction.
stateNumXor := uint64(commitTx.TxIn[0].Sequence&0xFFFFFF) << 24
stateNumXor |= uint64(commitTx.LockTime & 0xFFFFFF)

// Finally, to obtain the final state number, we XOR by the obfuscater
// value to de-obfuscate the state number.
Expand Down
90 changes: 79 additions & 11 deletions lnwallet/script_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"testing"
"time"

"github.com/btcsuite/fastsha256"
"github.com/roasbeef/btcd/btcec"
Expand Down Expand Up @@ -563,25 +564,92 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
}
}

var stateHintTests = []struct {
name string
from uint64
to uint64
inputs int
shouldFail bool
}{
{
name: "states 0 to 1000",
from: 0,
to: 1000,
inputs: 1,
shouldFail: false,
},
{
name: "states 'maxStateHint-1000' to 'maxStateHint'",
from: maxStateHint - 1000,
to: maxStateHint,
inputs: 1,
shouldFail: false,
},
{
name: "state 'maxStateHint+1'",
from: maxStateHint + 1,
to: maxStateHint + 10,
inputs: 1,
shouldFail: true,
},
{
name: "commit transaction with two inputs",
inputs: 2,
shouldFail: true,
},
}

func TestCommitTxStateHint(t *testing.T) {
commitTx := wire.NewMsgTx(2)
commitTx.AddTxIn(&wire.TxIn{})

var obsfucator [StateHintSize]byte
copy(obsfucator[:], testHdSeed[:StateHintSize])
timeYesterday := uint32(time.Now().Unix() - 24*60*60)

for i := 0; i < 10000; i++ {
stateNum := uint32(i)
for _, test := range stateHintTests {
commitTx := wire.NewMsgTx(2)

err := SetStateNumHint(commitTx, stateNum, obsfucator)
if err != nil {
t.Fatalf("unable to set state num %v: %v", i, err)
// Add supplied number of inputs to the commitment transaction.
for i := 0; i < test.inputs; i++ {
commitTx.AddTxIn(&wire.TxIn{})
}

extractedStateNum := GetStateNumHint(commitTx, obsfucator)
if extractedStateNum != stateNum {
t.Fatalf("state number mismatched, expected %v, got %v",
stateNum, extractedStateNum)
for i := test.from; i <= test.to; i++ {
stateNum := uint64(i)

err := SetStateNumHint(commitTx, stateNum, obsfucator)
if err != nil && !test.shouldFail {
t.Fatalf("unable to set state num %v: %v", i, err)
} else if err == nil && test.shouldFail {
t.Fatalf("Failed(%v): test should fail but did not", test.name)
}

locktime := commitTx.LockTime
sequence := commitTx.TxIn[0].Sequence

// Locktime should not be less than 500,000,000 and not larger
// than the time 24 hours ago. One day should provide a good
// enough buffer for the tests.
if locktime < 5e8 || locktime > timeYesterday {
if !test.shouldFail {
t.Fatalf("The value of locktime (%v) may cause the commitment "+
"transaction to be unspendable", locktime)
}
}

if sequence&wire.SequenceLockTimeDisabled == 0 {
if !test.shouldFail {
t.Fatalf("Sequence locktime is NOT disabled when it should be")
}
}

extractedStateNum := GetStateNumHint(commitTx, obsfucator)
if extractedStateNum != stateNum && !test.shouldFail {
t.Fatalf("state number mismatched, expected %v, got %v",
stateNum, extractedStateNum)
} else if extractedStateNum == stateNum && test.shouldFail {
t.Fatalf("Failed(%v): test should fail but did not", test.name)
}
}
t.Logf("Passed: %v", test.name)
}
}
2 changes: 1 addition & 1 deletion lnwallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@ func deriveStateHintObsfucator(elkremRoot *elkrem.ElkremSender) ([StateHintSize]
return obsfucator, nil
}

// initStateHints properly sets the obsfucated state ints ob both commitment
// 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 {
Expand Down
8 changes: 8 additions & 0 deletions lnwire/lnwire.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ func writeElement(w io.Writer, element interface{}) error {
return err
}

if _, err := w.Write(e[:]); err != nil {
return err
}
case [6]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
Expand Down Expand Up @@ -467,6 +471,10 @@ func readElement(r io.Reader, element interface{}) error {
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *[6]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case []byte:
if _, err := io.ReadFull(r, e); err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions lnwire/single_funding_complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ type SingleFundingComplete struct {
// the commitment transaction's only input. The initiator generates
// this value by hashing the 0th' state derived from the revocation PRF
// an additional time.
StateHintObsfucator [4]byte
StateHintObsfucator [6]byte
}

// NewSingleFundingComplete creates, and returns a new empty
// SingleFundingResponse.
func NewSingleFundingComplete(chanID uint64, fundingPoint wire.OutPoint,
commitSig *btcec.Signature, revokeKey *btcec.PublicKey,
obsfucator [4]byte) *SingleFundingComplete {
obsfucator [6]byte) *SingleFundingComplete {

return &SingleFundingComplete{
ChannelID: chanID,
Expand Down
4 changes: 2 additions & 2 deletions lnwire/single_funding_complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

func TestSingleFundingCompleteWire(t *testing.T) {
var obsfucator [4]byte
copy(obsfucator[:], bytes.Repeat([]byte("k"), 4))
var obsfucator [6]byte
copy(obsfucator[:], bytes.Repeat([]byte("k"), 6))

// First create a new SFC message.
sfc := NewSingleFundingComplete(22, *outpoint1, commitSig1, pubKey,
Expand Down