Skip to content

Commit

Permalink
Merge pull request #47 from guggero/sign-abstraction
Browse files Browse the repository at this point in the history
router: abstract node key behind ECDH interface
  • Loading branch information
Roasbeef authored May 1, 2020
2 parents 6fe91c1 + d019eaf commit 3c8c8d0
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 38 deletions.
5 changes: 4 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@ func main() {
}

privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), binKey)
privKeyECDH := &sphinx.PrivKeyECDH{PrivKey: privkey}
replayLog := sphinx.NewMemoryReplayLog()
s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, replayLog)
s := sphinx.NewRouter(
privKeyECDH, &chaincfg.TestNet3Params, replayLog,
)

replayLog.Start()
defer replayLog.Stop()
Expand Down
71 changes: 55 additions & 16 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,55 @@ const (
// the output of a SHA256 hash.
type Hash256 [sha256.Size]byte

// SingleKeyECDH is an abstraction interface that hides the implementation of an
// ECDH operation against a specific private key. We use this abstraction for
// the long term keys which we eventually want to be able to keep in a hardware
// wallet or HSM.
type SingleKeyECDH interface {
// PubKey returns the public key of the private key that is abstracted
// away by the interface.
PubKey() *btcec.PublicKey

// ECDH performs a scalar multiplication (ECDH-like operation) between
// the abstracted private key and a remote public key. The output
// returned will be the sha256 of the resulting shared point serialized
// in compressed format.
ECDH(pubKey *btcec.PublicKey) ([32]byte, error)
}

// PrivKeyECDH is an implementation of the SingleKeyECDH in which we do have the
// full private key. This can be used to wrap a temporary key to conform to the
// SingleKeyECDH interface.
type PrivKeyECDH struct {
// PrivKey is the private key that is used for the ECDH operation.
PrivKey *btcec.PrivateKey
}

// PubKey returns the public key of the private key that is abstracted away by
// the interface.
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PrivKeyECDH) PubKey() *btcec.PublicKey {
return p.PrivKey.PubKey()
}

// ECDH performs a scalar multiplication (ECDH-like operation) between the
// abstracted private key and a remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PrivKeyECDH) ECDH(pub *btcec.PublicKey) ([32]byte, error) {
s := &btcec.PublicKey{}
s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, p.PrivKey.D.Bytes())

return sha256.Sum256(s.SerializeCompressed()), nil
}

// DecryptedError contains the decrypted error message and its sender.
type DecryptedError struct {
// Sender is the node that sent the error. Note that a node may occur in
Expand Down Expand Up @@ -149,21 +198,7 @@ func (r *Router) generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) {
}

// Compute our shared secret.
sharedSecret = generateSharedSecret(dhKey, r.onionKey)
return sharedSecret, nil
}

// generateSharedSecret generates the shared secret for a particular hop. The
// shared secret is generated by taking the group element contained in the
// mix-header, and performing an ECDH operation with the node's long term onion
// key. We then take the _entire_ point generated by the ECDH operation,
// serialize that using a compressed format, then feed the raw bytes through a
// single SHA256 invocation. The resulting value is the shared secret.
func generateSharedSecret(pub *btcec.PublicKey, priv *btcec.PrivateKey) Hash256 {
s := &btcec.PublicKey{}
s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes())

return sha256.Sum256(s.SerializeCompressed())
return r.onionKey.ECDH(dhKey)
}

// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
Expand Down Expand Up @@ -200,10 +235,14 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
len(encryptedData))
}

sharedSecrets := generateSharedSecrets(
sharedSecrets, err := generateSharedSecrets(
o.circuit.PaymentPath,
o.circuit.SessionKey,
)
if err != nil {
return nil, fmt.Errorf("error generating shared secret: %v",
err)
}

var (
sender int
Expand Down
10 changes: 8 additions & 2 deletions obfuscation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ func TestOnionFailure(t *testing.T) {
errorPath := paymentPath[:len(paymentPath)-1]

failureData := bytes.Repeat([]byte{'A'}, onionErrorLength-sha256.Size)
sharedSecrets := generateSharedSecrets(paymentPath, sessionKey)
sharedSecrets, err := generateSharedSecrets(paymentPath, sessionKey)
if err != nil {
t.Fatalf("Unexpected error while generating secrets: %v", err)
}

// Emulate creation of the obfuscator on node where error have occurred.
obfuscator := &OnionErrorEncrypter{
Expand Down Expand Up @@ -194,7 +197,10 @@ func TestOnionFailureSpecVector(t *testing.T) {
}

var obfuscatedData []byte
sharedSecrets := generateSharedSecrets(paymentPath, sessionKey)
sharedSecrets, err := generateSharedSecrets(paymentPath, sessionKey)
if err != nil {
t.Fatalf("Unexpected error while generating secrets: %v", err)
}
for i, test := range onionErrorData {

// Decode the shared secret and check that it matchs with
Expand Down
36 changes: 19 additions & 17 deletions sphinx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package sphinx

import (
"bytes"
"crypto/ecdsa"
"crypto/hmac"
"crypto/sha256"
"fmt"
Expand Down Expand Up @@ -117,7 +116,7 @@ type OnionPacket struct {
// generateSharedSecrets by the given nodes pubkeys, generates the shared
// secrets.
func generateSharedSecrets(paymentPath []*btcec.PublicKey,
sessionKey *btcec.PrivateKey) []Hash256 {
sessionKey *btcec.PrivateKey) ([]Hash256, error) {

// Each hop performs ECDH with our ephemeral key pair to arrive at a
// shared secret. Additionally, each hop randomizes the group element
Expand All @@ -131,8 +130,15 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
// Within the loop each new triplet will be computed recursively based
// off of the blinding factor of the last hop.
lastEphemeralPubKey := sessionKey.PubKey()
hopSharedSecrets[0] = generateSharedSecret(paymentPath[0], sessionKey)
lastBlindingFactor := computeBlindingFactor(lastEphemeralPubKey, hopSharedSecrets[0][:])
sessionKeyECDH := &PrivKeyECDH{PrivKey: sessionKey}
sharedSecret, err := sessionKeyECDH.ECDH(paymentPath[0])
if err != nil {
return nil, err
}
hopSharedSecrets[0] = sharedSecret
lastBlindingFactor := computeBlindingFactor(
lastEphemeralPubKey, hopSharedSecrets[0][:],
)

// The cached blinding factor will contain the running product of the
// session private key x and blinding factors b_i, computed as
Expand Down Expand Up @@ -184,7 +190,7 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
)
}

return hopSharedSecrets
return hopSharedSecrets, nil
}

// NewOnionPacket creates a new onion packet which is capable of obliviously
Expand All @@ -211,9 +217,12 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
return nil, fmt.Errorf("packet filler must be specified")
}

hopSharedSecrets := generateSharedSecrets(
hopSharedSecrets, err := generateSharedSecrets(
paymentPath.NodeKeys(), sessionKey,
)
if err != nil {
return nil, fmt.Errorf("error generating shared secret: %v", err)
}

// Generate the padding, called "filler strings" in the paper.
filler := generateHeaderPadding("rho", paymentPath, hopSharedSecrets)
Expand Down Expand Up @@ -479,14 +488,14 @@ type Router struct {
nodeID [AddressSize]byte
nodeAddr *btcutil.AddressPubKeyHash

onionKey *btcec.PrivateKey
onionKey SingleKeyECDH

log ReplayLog
}

// NewRouter creates a new instance of a Sphinx onion Router given the node's
// currently advertised onion private key, and the target Bitcoin network.
func NewRouter(nodeKey *btcec.PrivateKey, net *chaincfg.Params, log ReplayLog) *Router {
func NewRouter(nodeKey SingleKeyECDH, net *chaincfg.Params, log ReplayLog) *Router {
var nodeID [AddressSize]byte
copy(nodeID[:], btcutil.Hash160(nodeKey.PubKey().SerializeCompressed()))

Expand All @@ -496,15 +505,8 @@ func NewRouter(nodeKey *btcec.PrivateKey, net *chaincfg.Params, log ReplayLog) *
return &Router{
nodeID: nodeID,
nodeAddr: nodeAddr,
onionKey: &btcec.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: btcec.S256(),
X: nodeKey.X,
Y: nodeKey.Y,
},
D: nodeKey.D,
},
log: log,
onionKey: nodeKey,
log: log,
}
}

Expand Down
6 changes: 4 additions & 2 deletions sphinx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke
}

nodes[i] = NewRouter(
privKey, &chaincfg.MainNetParams, NewMemoryReplayLog(),
&PrivKeyECDH{PrivKey: privKey}, &chaincfg.MainNetParams,
NewMemoryReplayLog(),
)
}

Expand Down Expand Up @@ -493,7 +494,8 @@ func newEOBRoute(numHops uint32,
}

nodes[i] = NewRouter(
privKey, &chaincfg.MainNetParams, NewMemoryReplayLog(),
&PrivKeyECDH{PrivKey: privKey}, &chaincfg.MainNetParams,
NewMemoryReplayLog(),
)
}

Expand Down

0 comments on commit 3c8c8d0

Please sign in to comment.