Skip to content

Commit

Permalink
add hold times [squash]
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Nov 16, 2022
1 parent bb61d8d commit 1e5ca3e
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 26 deletions.
85 changes: 65 additions & 20 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"

"github.com/aead/chacha20"
Expand All @@ -18,6 +20,8 @@ const (
HMACSize = 32
)

var byteOrder = binary.BigEndian

// Hash256 is a statically sized, 32-byte array, typically containing
// the output of a SHA256 hash.
type Hash256 [sha256.Size]byte
Expand Down Expand Up @@ -91,6 +95,10 @@ type DecryptedError struct {

// Message is the decrypted error message.
Message []byte

// HoldTimesMs is an array of millisecond durations reported by each node on
// the (error) path.
HoldTimesMs []uint64
}

// zeroHMAC is the special HMAC value that allows the final node to determine
Expand Down Expand Up @@ -275,6 +283,7 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
// We'll iterate a constant amount of hops to ensure that we don't give
// away an timing information pertaining to the position in the route
// that the error emanated from.
holdTimesMs := make([]uint64, 0)
for i := 0; i < NumMaxHops; i++ {
var sharedSecret Hash256

Expand All @@ -294,9 +303,6 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (

message, payloads, hmacs := getMsgComponents(encryptedData)

final := payloads[0] == payloadFinal
// TODO: Extract hold time from payload.

expectedHmac := calculateHmac(sharedSecret, i, message, payloads, hmacs)
actualHmac := hmacs[i*sha256.Size : (i+1)*sha256.Size]

Expand All @@ -306,11 +312,23 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
msg = nil
}

// If we are at the node that is the source of the error, we can now
// save the message in our return variable.
if final && sender == 0 {
sender = i + 1
msg = message
// Extract the payload and exit with a nil message if it is invalid.
payloadType, holdTimeMs, err := extractPayload(payloads)
if sender == 0 {
if err != nil {
sender = i + 1
msg = nil
}

// Store hold time reported by this node.
holdTimesMs = append(holdTimesMs, holdTimeMs)

// If we are at the node that is the source of the error, we can now
// save the message in our return variable.
if payloadType == payloadFinal {
sender = i + 1
msg = message
}
}

// Shift payloads and hmacs to the left to prepare for the next
Expand All @@ -329,9 +347,10 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
}

return &DecryptedError{
SenderIdx: sender,
Sender: o.circuit.PaymentPath[sender-1],
Message: msg,
SenderIdx: sender,
Sender: o.circuit.PaymentPath[sender-1],
Message: msg,
HoldTimesMs: holdTimesMs,
}, nil
}

Expand Down Expand Up @@ -472,11 +491,13 @@ func (o *OnionErrorEncrypter) addHmacs(data []byte) {
// The reason for using onion obfuscation is to not give
// away to the nodes in the payment path the information about the exact
// failure and its origin.
func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte {
func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte,
holdTimeMs uint64) []byte {

if initial {
data = o.initializePayload(data)
data = o.initializePayload(data, holdTimeMs)
} else {
o.addIntermediatePayload(data)
o.addIntermediatePayload(data, holdTimeMs)
}

// Update hmac block.
Expand All @@ -486,28 +507,52 @@ func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte {
return onionEncrypt(&o.sharedSecret, data)
}

func (o *OnionErrorEncrypter) initializePayload(message []byte) []byte {
func (o *OnionErrorEncrypter) initializePayload(message []byte,
holdTimeMs uint64) []byte {

// Add space for payloads and hmacs.
data := make([]byte, len(message)+hmacsAndPayloadsLen)
copy(data, message)

_, payloads, _ := getMsgComponents(data)

// Signal final hops in the payload.
// TODO: Add hold time to payload.
payloads[0] = payloadFinal
addPayload(payloads, payloadFinal, holdTimeMs)

return data
}

func (o *OnionErrorEncrypter) addIntermediatePayload(data []byte) {
func addPayload(payloads []byte, payloadType PayloadType, holdTimeMs uint64) {
byteOrder.PutUint64(payloads[1:], uint64(holdTimeMs))

payloads[0] = byte(payloadType)
}

func extractPayload(payloads []byte) (PayloadType, uint64, error) {
var payloadType PayloadType

switch payloads[0] {
case payloadFinal, payloadIntermediate:
payloadType = PayloadType(payloads[0])

default:
return 0, 0, errors.New("invalid payload type")
}

holdTimeMs := byteOrder.Uint64(payloads[1:])

return payloadType, holdTimeMs, nil
}

func (o *OnionErrorEncrypter) addIntermediatePayload(data []byte,
holdTimeMs uint64) {

_, payloads, hmacs := getMsgComponents(data)

// Shift hmacs and payloads to create space for the payload.
shiftPayloadsRight(payloads)
shiftHmacsRight(hmacs)

// Signal intermediate hop in the payload.
// TODO: Add hold time to payload.
payloads[0] = payloadIntermediate
addPayload(payloads, payloadIntermediate, holdTimeMs)
}
21 changes: 15 additions & 6 deletions obfuscation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func TestOnionFailure(t *testing.T) {

// Emulate the situation when last hop creates the onion failure
// message and send it back.
obfuscatedData := obfuscator.EncryptError(true, failureData)
const finalHoldTimeMs = 1
obfuscatedData := obfuscator.EncryptError(true, failureData, finalHoldTimeMs)
holdTimesMs := []uint64{finalHoldTimeMs}

// Emulate that failure message is backward obfuscated on every hop.
for i := len(errorPath) - 2; i >= 0; i-- {
Expand All @@ -51,7 +53,12 @@ func TestOnionFailure(t *testing.T) {
obfuscator = &OnionErrorEncrypter{
sharedSecret: sharedSecrets[i],
}
obfuscatedData = obfuscator.EncryptError(false, obfuscatedData)

intermediateHoldTimeMs := uint64(100 + i)
obfuscatedData = obfuscator.EncryptError(
false, obfuscatedData, intermediateHoldTimeMs,
)
holdTimesMs = append([]uint64{intermediateHoldTimeMs}, holdTimesMs...)
}

// Emulate creation of the deobfuscator on the receiving onion error side.
Expand Down Expand Up @@ -84,6 +91,8 @@ func TestOnionFailure(t *testing.T) {
t.Fatalf("data not equals, expected: \"%v\", real: \"%v\"",
string(failureData), string(decryptedError.Message))
}

require.Equal(t, holdTimesMs, decryptedError.HoldTimesMs)
}

// TestOnionFailureCorruption checks the ability of sender of payment to
Expand Down Expand Up @@ -114,7 +123,7 @@ func TestOnionFailureCorruption(t *testing.T) {

// Emulate the situation when last hop creates the onion failure
// message and send it back.
obfuscatedData := obfuscator.EncryptError(true, failureData)
obfuscatedData := obfuscator.EncryptError(true, failureData, 1)

// Emulate that failure message is backward obfuscated on every hop.
for i := len(errorPath) - 2; i >= 0; i-- {
Expand All @@ -123,7 +132,7 @@ func TestOnionFailureCorruption(t *testing.T) {
obfuscator = &OnionErrorEncrypter{
sharedSecret: sharedSecrets[i],
}
obfuscatedData = obfuscator.EncryptError(false, obfuscatedData)
obfuscatedData = obfuscator.EncryptError(false, obfuscatedData, uint64(100+i))

// Hop 1 (the second hop from the sender pov) is corrupting the failure
// message.
Expand Down Expand Up @@ -302,11 +311,11 @@ func TestOnionFailureSpecVector(t *testing.T) {
if i == 0 {
// Emulate the situation when last hop creates the onion failure
// message and send it back.
obfuscatedData = obfuscator.EncryptError(true, failureData)
obfuscatedData = obfuscator.EncryptError(true, failureData, 0)
} else {
// Emulate the situation when forward node obfuscates
// the onion failure.
obfuscatedData = obfuscator.EncryptError(false, obfuscatedData)
obfuscatedData = obfuscator.EncryptError(false, obfuscatedData, 0)
}

// Decode the obfuscated data and check that it matches the
Expand Down

0 comments on commit 1e5ca3e

Please sign in to comment.