Skip to content

Commit

Permalink
failure_onion thing.
Browse files Browse the repository at this point in the history
  • Loading branch information
fiatjaf committed Nov 7, 2020
1 parent bbf3c81 commit 7cee54e
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 6 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.14

require (
github.com/aarzilli/golua v0.0.0-20190714183732-fc27908ace94
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46
github.com/btcsuite/btcutil v1.0.2
github.com/elazarl/go-bindata-assetfs v1.0.0
Expand All @@ -23,6 +24,7 @@ require (
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/kelseyhightower/envconfig v1.4.0
github.com/lib/pq v1.7.0
github.com/lightningnetwork/lightning-onion v1.0.1
github.com/lightningnetwork/lnd v0.10.1-beta
github.com/lucsky/cuid v1.0.2
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aarzilli/golua v0.0.0-20190714183732-fc27908ace94 h1:BdnmhcIVWErdfypFo/f9Ai2KoC2BpkLpGSyv0aquYuI=
github.com/aarzilli/golua v0.0.0-20190714183732-fc27908ace94/go.mod h1:SV7PRru1lATaCdZXNt5AZHw0uKiN1BoYBHrcctQ1Ouc=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
Expand Down Expand Up @@ -179,6 +180,7 @@ github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.10.1-beta h1:zA/rQoxC5FNHtayVuA2wRtSOEDnJbuzAzHKAf2PWj1Q=
github.com/lightningnetwork/lnd v0.10.1-beta/go.mod h1:F9er1DrpOHdQVQBqYqyBqIFyl6q16xgBM8yTioHj2Cg=
Expand Down
122 changes: 116 additions & 6 deletions payment_receive.go → htlc_accepted.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package main

import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"database/sql"
"encoding/hex"
"strconv"
"time"

"github.com/aead/chacha20"
"github.com/btcsuite/btcd/btcec"
"github.com/fiatjaf/etleneum/types"
"github.com/fiatjaf/lightningd-gjson-rpc/plugin"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
)

var continueHTLC = map[string]interface{}{"result": "continue"}
var failHTLC = map[string]interface{}{"result": "fail", "failure_code": 16392}
var failUnknown = map[string]interface{}{"result": "fail", "failure_code": 16399}

func htlc_accepted(p *plugin.Plugin, params plugin.Params) (resp interface{}) {
amount := params.Get("htlc.amount").String()
Expand Down Expand Up @@ -52,25 +57,94 @@ func htlc_accepted(p *plugin.Plugin, params plugin.Params) (resp interface{}) {
return continueHTLC
}

if id[0] != 'c' && id[0] != 'r' {
// it's not an invoice for an etleneum call or contract
p.Logf("parsed id is not an etleneum payment (%s) - continue", id)
return continueHTLC
}

// ensure that we can derive the correct preimage for this payment
preimage := makePreimage(id)
preimageHex := hex.EncodeToString(preimage)
derivedHash := sha256.Sum256(preimage)
derivedHashHex := hex.EncodeToString(derivedHash[:])
if hash != derivedHashHex {
p.Logf("we have a preimage %s, but its hash %s didn't match the expected hash %s - fail with incorrect_or_unknown_payment_details", preimageHex, derivedHashHex, hash)
return failUnknown

// get keys stuff so we can return a wrapped onion to pre-pay probes
nextOnion, err := hex.DecodeString(params.Get("onion.next_onion").String())
if err != nil {
p.Logf("lightningd has sent us an invalid onion.next_onion: %s",
err.Error())
return failHTLC
}

var nextOnionPacket sphinx.OnionPacket
err = nextOnionPacket.Decode(bytes.NewBuffer(nextOnion))
if err != nil {
p.Logf("couldn't parse onion.next_onion: %s", err.Error())
return failHTLC
}

// private key for the last hop
var ctid string
if id[0] == 'c' {
ctid = id
} else if id[0] == 'r' {
call, err := callFromRedis(id)
if err != nil {
return continueHTLC
}
ctid = call.ContractId
}
lastHopKey, _ := makeKeys(ctid)

// bolt04 shared key stuff: ecdh() then sha256()
s := &btcec.PublicKey{}
s.X, s.Y = btcec.S256().ScalarMult(
nextOnionPacket.EphemeralKey.X,
nextOnionPacket.EphemeralKey.Y,
lastHopKey.D.Bytes(),
)
lastHopSharedSecret := sha256.Sum256(s.SerializeCompressed())

// produce the error as if we were the last hop
failure := lnwire.NewFailIncorrectDetails(lnwire.MilliSatoshi(msatoshi), 0)
var payload bytes.Buffer
if err := lnwire.EncodeFailure(&payload, failure, 0); err != nil {
panic(err)
}
data := payload.Bytes()

// hmac the payload
umKey := generateKey("um", lastHopSharedSecret[:])
mac := hmac.New(sha256.New, umKey[:])
mac.Write(data)
h := mac.Sum(nil)
failureOnion := append(h, data...)

// obfuscate/wrap the message as if we were the last hop
ammagKey := generateKey("ammag", lastHopSharedSecret[:])
placeholder := make([]byte, len(failureOnion))
xor(
placeholder,
failureOnion,
generateCipherStream(ammagKey, uint(len(failureOnion))),
)
failureOnion = placeholder

// return the onion as failure_onion and lightningd will wrap it
return map[string]interface{}{
"result": "fail",
"failure_onion": hex.EncodeToString(failureOnion),
}
}

// run the call
if id[0] == 'c' {
ok = contractPaymentReceived(id, msatoshi)
} else if id[0] == 'r' {
ok = callPaymentReceived(id, msatoshi)
} else {
// it's not an invoice for an etleneum call or contract
p.Logf("parsed id is not an etleneum payment (%s) - continue", id)
return continueHTLC
}

// after the call succeeds, we resolve the payment
Expand Down Expand Up @@ -218,3 +292,39 @@ func callPaymentReceived(callId string, msatoshi int64) (ok bool) {

return true
}

func generateCipherStream(key [32]byte, numBytes uint) []byte {
var (
nonce [8]byte
)
cipher, err := chacha20.NewCipher(nonce[:], key[:])
if err != nil {
panic(err)
}
output := make([]byte, numBytes)
cipher.XORKeyStream(output, output)

return output
}

func xor(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
}

func generateKey(keyType string, sharedKey []byte) [32]byte {
mac := hmac.New(sha256.New, []byte(keyType))
mac.Write(sharedKey)
h := mac.Sum(nil)

var key [32]byte
copy(key[:], h[:32])

return key
}

0 comments on commit 7cee54e

Please sign in to comment.