Skip to content

Commit 3c8c8d0

Browse files
authored
Merge pull request #47 from guggero/sign-abstraction
router: abstract node key behind ECDH interface
2 parents 6fe91c1 + d019eaf commit 3c8c8d0

File tree

5 files changed

+90
-38
lines changed

5 files changed

+90
-38
lines changed

cmd/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,11 @@ func main() {
133133
}
134134

135135
privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), binKey)
136+
privKeyECDH := &sphinx.PrivKeyECDH{PrivKey: privkey}
136137
replayLog := sphinx.NewMemoryReplayLog()
137-
s := sphinx.NewRouter(privkey, &chaincfg.TestNet3Params, replayLog)
138+
s := sphinx.NewRouter(
139+
privKeyECDH, &chaincfg.TestNet3Params, replayLog,
140+
)
138141

139142
replayLog.Start()
140143
defer replayLog.Stop()

crypto.go

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,55 @@ const (
2222
// the output of a SHA256 hash.
2323
type Hash256 [sha256.Size]byte
2424

25+
// SingleKeyECDH is an abstraction interface that hides the implementation of an
26+
// ECDH operation against a specific private key. We use this abstraction for
27+
// the long term keys which we eventually want to be able to keep in a hardware
28+
// wallet or HSM.
29+
type SingleKeyECDH interface {
30+
// PubKey returns the public key of the private key that is abstracted
31+
// away by the interface.
32+
PubKey() *btcec.PublicKey
33+
34+
// ECDH performs a scalar multiplication (ECDH-like operation) between
35+
// the abstracted private key and a remote public key. The output
36+
// returned will be the sha256 of the resulting shared point serialized
37+
// in compressed format.
38+
ECDH(pubKey *btcec.PublicKey) ([32]byte, error)
39+
}
40+
41+
// PrivKeyECDH is an implementation of the SingleKeyECDH in which we do have the
42+
// full private key. This can be used to wrap a temporary key to conform to the
43+
// SingleKeyECDH interface.
44+
type PrivKeyECDH struct {
45+
// PrivKey is the private key that is used for the ECDH operation.
46+
PrivKey *btcec.PrivateKey
47+
}
48+
49+
// PubKey returns the public key of the private key that is abstracted away by
50+
// the interface.
51+
//
52+
// NOTE: This is part of the SingleKeyECDH interface.
53+
func (p *PrivKeyECDH) PubKey() *btcec.PublicKey {
54+
return p.PrivKey.PubKey()
55+
}
56+
57+
// ECDH performs a scalar multiplication (ECDH-like operation) between the
58+
// abstracted private key and a remote public key. The output returned will be
59+
// the sha256 of the resulting shared point serialized in compressed format. If
60+
// k is our private key, and P is the public key, we perform the following
61+
// operation:
62+
//
63+
// sx := k*P
64+
// s := sha256(sx.SerializeCompressed())
65+
//
66+
// NOTE: This is part of the SingleKeyECDH interface.
67+
func (p *PrivKeyECDH) ECDH(pub *btcec.PublicKey) ([32]byte, error) {
68+
s := &btcec.PublicKey{}
69+
s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, p.PrivKey.D.Bytes())
70+
71+
return sha256.Sum256(s.SerializeCompressed()), nil
72+
}
73+
2574
// DecryptedError contains the decrypted error message and its sender.
2675
type DecryptedError struct {
2776
// Sender is the node that sent the error. Note that a node may occur in
@@ -149,21 +198,7 @@ func (r *Router) generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) {
149198
}
150199

151200
// Compute our shared secret.
152-
sharedSecret = generateSharedSecret(dhKey, r.onionKey)
153-
return sharedSecret, nil
154-
}
155-
156-
// generateSharedSecret generates the shared secret for a particular hop. The
157-
// shared secret is generated by taking the group element contained in the
158-
// mix-header, and performing an ECDH operation with the node's long term onion
159-
// key. We then take the _entire_ point generated by the ECDH operation,
160-
// serialize that using a compressed format, then feed the raw bytes through a
161-
// single SHA256 invocation. The resulting value is the shared secret.
162-
func generateSharedSecret(pub *btcec.PublicKey, priv *btcec.PrivateKey) Hash256 {
163-
s := &btcec.PublicKey{}
164-
s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes())
165-
166-
return sha256.Sum256(s.SerializeCompressed())
201+
return r.onionKey.ECDH(dhKey)
167202
}
168203

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

203-
sharedSecrets := generateSharedSecrets(
238+
sharedSecrets, err := generateSharedSecrets(
204239
o.circuit.PaymentPath,
205240
o.circuit.SessionKey,
206241
)
242+
if err != nil {
243+
return nil, fmt.Errorf("error generating shared secret: %v",
244+
err)
245+
}
207246

208247
var (
209248
sender int

obfuscation_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ func TestOnionFailure(t *testing.T) {
3030
errorPath := paymentPath[:len(paymentPath)-1]
3131

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

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

196199
var obfuscatedData []byte
197-
sharedSecrets := generateSharedSecrets(paymentPath, sessionKey)
200+
sharedSecrets, err := generateSharedSecrets(paymentPath, sessionKey)
201+
if err != nil {
202+
t.Fatalf("Unexpected error while generating secrets: %v", err)
203+
}
198204
for i, test := range onionErrorData {
199205

200206
// Decode the shared secret and check that it matchs with

sphinx.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package sphinx
22

33
import (
44
"bytes"
5-
"crypto/ecdsa"
65
"crypto/hmac"
76
"crypto/sha256"
87
"fmt"
@@ -117,7 +116,7 @@ type OnionPacket struct {
117116
// generateSharedSecrets by the given nodes pubkeys, generates the shared
118117
// secrets.
119118
func generateSharedSecrets(paymentPath []*btcec.PublicKey,
120-
sessionKey *btcec.PrivateKey) []Hash256 {
119+
sessionKey *btcec.PrivateKey) ([]Hash256, error) {
121120

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

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

187-
return hopSharedSecrets
193+
return hopSharedSecrets, nil
188194
}
189195

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

214-
hopSharedSecrets := generateSharedSecrets(
220+
hopSharedSecrets, err := generateSharedSecrets(
215221
paymentPath.NodeKeys(), sessionKey,
216222
)
223+
if err != nil {
224+
return nil, fmt.Errorf("error generating shared secret: %v", err)
225+
}
217226

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

482-
onionKey *btcec.PrivateKey
491+
onionKey SingleKeyECDH
483492

484493
log ReplayLog
485494
}
486495

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

@@ -496,15 +505,8 @@ func NewRouter(nodeKey *btcec.PrivateKey, net *chaincfg.Params, log ReplayLog) *
496505
return &Router{
497506
nodeID: nodeID,
498507
nodeAddr: nodeAddr,
499-
onionKey: &btcec.PrivateKey{
500-
PublicKey: ecdsa.PublicKey{
501-
Curve: btcec.S256(),
502-
X: nodeKey.X,
503-
Y: nodeKey.Y,
504-
},
505-
D: nodeKey.D,
506-
},
507-
log: log,
508+
onionKey: nodeKey,
509+
log: log,
508510
}
509511
}
510512

sphinx_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke
5050
}
5151

5252
nodes[i] = NewRouter(
53-
privKey, &chaincfg.MainNetParams, NewMemoryReplayLog(),
53+
&PrivKeyECDH{PrivKey: privKey}, &chaincfg.MainNetParams,
54+
NewMemoryReplayLog(),
5455
)
5556
}
5657

@@ -493,7 +494,8 @@ func newEOBRoute(numHops uint32,
493494
}
494495

495496
nodes[i] = NewRouter(
496-
privKey, &chaincfg.MainNetParams, NewMemoryReplayLog(),
497+
&PrivKeyECDH{PrivKey: privKey}, &chaincfg.MainNetParams,
498+
NewMemoryReplayLog(),
497499
)
498500
}
499501

0 commit comments

Comments
 (0)