Пример #1
0
// authPKH...
func (c *LNDConn) authPKH(
	myId *btcec.PrivateKey, theirPKH, localEphPubBytes []byte) error {
	if c.Authed {
		return fmt.Errorf("%s already authed", c.RemotePub)
	}
	if len(theirPKH) != 20 {
		return fmt.Errorf("remote PKH must be 20 bytes, got %d",
			len(theirPKH))
	}

	// Send 53 bytes: our pubkey, and the remote's pubkey hash.
	var greetingMsg [53]byte
	copy(greetingMsg[:33], myId.PubKey().SerializeCompressed())
	copy(greetingMsg[:33], theirPKH)
	if _, err := c.Conn.Write(greetingMsg[:]); err != nil {
		return err
	}

	// Wait for their response.
	// TODO(tadge): add timeout here
	//  * NOTE(roasbeef): read timeout should be set on the underlying
	//    net.Conn.
	resp := make([]byte, 53)
	if _, err := c.Conn.Read(resp); err != nil {
		return err
	}

	// Parse their long-term public key, and generate the DH proof.
	theirPub, err := btcec.ParsePubKey(resp[:33], btcec.S256())
	if err != nil {
		return err
	}
	idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
	fmt.Printf("made idDH %x\n", idDH)
	theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))

	// Verify that their DH proof matches the one we just generated.
	if bytes.Equal(resp[33:], theirDHproof) == false {
		return fmt.Errorf("Invalid DH proof %x", theirDHproof)
	}

	// If their DH proof checks out, then send our own.
	myDHproof := btcutil.Hash160(append(c.RemotePub.SerializeCompressed(), idDH[:]...))
	if _, err = c.Conn.Write(myDHproof); err != nil {
		return err
	}

	// Proof sent, auth complete.
	c.RemotePub = theirPub
	theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
	copy(c.RemoteLNId[:], theirAdr[:16])
	c.Authed = true

	return nil
}
Пример #2
0
func TestGenerateSharedSecret(t *testing.T) {
	privKey1, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Errorf("private key generation error: %s", err)
		return
	}
	privKey2, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Errorf("private key generation error: %s", err)
		return
	}

	secret1 := btcec.GenerateSharedSecret(privKey1, privKey2.PubKey())
	secret2 := btcec.GenerateSharedSecret(privKey2, privKey1.PubKey())

	if !bytes.Equal(secret1, secret2) {
		t.Errorf("ECDH failed, secrets mismatch - first: %x, second: %x",
			secret1, secret2)
	}
}
Пример #3
0
// createCipherConn....
func (l *Listener) createCipherConn(lnConn *LNDConn) (*btcec.PrivateKey, error) {
	var err error
	var theirEphPubBytes []byte

	// First, read and deserialize their ephemeral public key.
	theirEphPubBytes, err = readClear(lnConn.Conn)
	if err != nil {
		return nil, err
	}
	if len(theirEphPubBytes) != 33 {
		return nil, fmt.Errorf("Got invalid %d byte eph pubkey %x\n",
			len(theirEphPubBytes), theirEphPubBytes)
	}
	theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256())
	if err != nil {
		return nil, err
	}

	// Once we've parsed and verified their key, generate, and send own
	// ephemeral key pair for use within this session.
	myEph, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return nil, err
	}
	if _, err := writeClear(lnConn.Conn, myEph.PubKey().SerializeCompressed()); err != nil {
		return nil, err
	}

	// Now that we have both keys, do non-interactive diffie with ephemeral
	// pubkeys, sha256 for good luck.
	sessionKey := fastsha256.Sum256(
		btcec.GenerateSharedSecret(myEph, theirEphPub),
	)

	lnConn.chachaStream, err = chacha20poly1305.New(sessionKey[:])

	// display private key for debug only
	fmt.Printf("made session key %x\n", sessionKey)

	lnConn.remoteNonceInt = 1 << 63
	lnConn.myNonceInt = 0

	lnConn.RemotePub = theirEphPub
	lnConn.Authed = false

	return myEph, nil
}
Пример #4
0
// authPubKey...
func (c *LNDConn) authPubKey(
	myId *btcec.PrivateKey, remotePubBytes, localEphPubBytes []byte) error {
	if c.Authed {
		return fmt.Errorf("%s already authed", c.RemotePub)
	}

	// Since we already know their public key, we can immediately generate
	// the DH proof without an additional round-trip.
	theirPub, err := btcec.ParsePubKey(remotePubBytes, btcec.S256())
	if err != nil {
		return err
	}
	theirPKH := btcutil.Hash160(remotePubBytes)
	idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
	myDHproof := btcutil.Hash160(append(c.RemotePub.SerializeCompressed(), idDH[:]...))

	// Send over the 73 byte authentication message: my pubkey, their
	// pubkey hash, DH proof.
	var authMsg [73]byte
	copy(authMsg[:33], myId.PubKey().SerializeCompressed())
	copy(authMsg[33:], theirPKH)
	copy(authMsg[53:], myDHproof)
	if _, err = c.Conn.Write(authMsg[:]); err != nil {
		return nil
	}

	// Await, their response. They should send only the 20-byte DH proof.
	resp := make([]byte, 20)
	_, err = c.Conn.Read(resp)
	if err != nil {
		return err
	}

	// Verify that their proof matches our locally computed version.
	theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))
	if bytes.Equal(resp, theirDHproof) == false {
		return fmt.Errorf("invalid DH proof %x", theirDHproof)
	}

	// Proof checks out, auth complete.
	c.RemotePub = theirPub
	theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
	copy(c.RemoteLNId[:], theirAdr[:16])
	c.Authed = true

	return nil
}
Пример #5
0
// Dial...
func (c *Conn) Dial(address string, remoteId []byte) error {
	var err error
	if c.conn != nil {
		return fmt.Errorf("connection already established")
	}

	// Before dialing out to the remote host, verify that `remoteId` is either
	// a pubkey or a pubkey hash.
	if len(remoteId) != 33 && len(remoteId) != 20 {
		return fmt.Errorf("must supply either remote pubkey or " +
			"pubkey hash")
	}

	// First, open the TCP connection itself.
	c.conn, err = net.Dial("tcp", address)
	if err != nil {
		return err
	}

	// Calc remote LNId; need this for creating pbx connections just because
	// LNid is in the struct does not mean it's authed!
	if len(remoteId) == 20 {
		copy(c.remoteLNId[:], remoteId[:16])
	} else {
		theirAdr := btcutil.Hash160(remoteId)
		copy(c.remoteLNId[:], theirAdr[:16])
	}

	// Make up an ephemeral keypair for this session.
	ourEphemeralPriv, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return err
	}
	ourEphemeralPub := ourEphemeralPriv.PubKey()

	// Sned 1. Send my ephemeral pubkey. Can add version bits.
	if _, err = writeClear(c.conn, ourEphemeralPub.SerializeCompressed()); err != nil {
		return err
	}

	// Read, then deserialize their ephemeral public key.
	theirEphPubBytes, err := readClear(c.conn)
	if err != nil {
		return err
	}
	theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256())
	if err != nil {
		return err
	}

	// Do non-interactive diffie with ephemeral pubkeys. Sha256 for good
	// luck.
	sessionKey := fastsha256.Sum256(
		btcec.GenerateSharedSecret(ourEphemeralPriv, theirEphPub),
	)

	// Now that we've derive the session key, we can initialize the
	// chacha20poly1305 AEAD instance which will be used for the remainder of
	// the session.
	c.chachaStream, err = chacha20poly1305.New(sessionKey[:])
	if err != nil {
		return err
	}

	// display private key for debug only
	fmt.Printf("made session key %x\n", sessionKey)

	c.myNonceInt = 1 << 63
	c.remoteNonceInt = 0

	c.remotePub = theirEphPub
	c.authed = false

	// Session is now open and confidential but not yet authenticated...
	// So auth!
	if len(remoteId) == 20 {
		// Only know pubkey hash (20 bytes).
		err = c.authPKH(remoteId, ourEphemeralPub.SerializeCompressed())
	} else {
		// Must be 33 byte pubkey.
		err = c.authPubKey(remoteId, ourEphemeralPub.SerializeCompressed())
	}
	if err != nil {
		return err
	}

	return nil
}
Пример #6
0
// AuthListen...
func (l *Listener) authenticateConnection(
	myId *btcec.PrivateKey, lnConn *LNDConn, localEphPubBytes []byte) error {
	var err error

	// TODO(roasbeef): should be using read/write clear here?
	slice := make([]byte, 73)
	n, err := lnConn.Conn.Read(slice)
	if err != nil {
		fmt.Printf("Read error: %s\n", err.Error())
		return err
	}

	fmt.Printf("read %d bytes\n", n)
	authmsg := slice[:n]
	if len(authmsg) != 53 && len(authmsg) != 73 {
		return fmt.Errorf("got auth message of %d bytes, "+
			"expect 53 or 73", len(authmsg))
	}

	myPKH := btcutil.Hash160(l.longTermPriv.PubKey().SerializeCompressed())
	if !bytes.Equal(myPKH, authmsg[33:53]) {
		return fmt.Errorf(
			"remote host asking for PKH %x, that's not me", authmsg[33:53])
	}

	// do DH with id keys
	theirPub, err := btcec.ParsePubKey(authmsg[:33], btcec.S256())
	if err != nil {
		return err
	}
	idDH := fastsha256.Sum256(
		btcec.GenerateSharedSecret(l.longTermPriv, theirPub),
	)

	myDHproof := btcutil.Hash160(
		append(lnConn.RemotePub.SerializeCompressed(), idDH[:]...),
	)
	theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))

	// If they already know our public key, then execute the fast path.
	// Verify their DH proof, and send our own.
	if len(authmsg) == 73 {
		// Verify their DH proof.
		if !bytes.Equal(authmsg[53:], theirDHproof) {
			return fmt.Errorf("invalid DH proof from %s",
				lnConn.RemoteAddr().String())
		}

		// Their DH proof checks out, so send ours now.
		if _, err = lnConn.Conn.Write(myDHproof); err != nil {
			return err
		}
	} else {
		// Otherwise, they don't yet know our public key. So we'll send
		// it over to them, so we can both compute the DH proof.
		msg := append(l.longTermPriv.PubKey().SerializeCompressed(), myDHproof...)
		if _, err = lnConn.Conn.Write(msg); err != nil {
			return err
		}

		resp := make([]byte, 20)
		if _, err := lnConn.Conn.Read(resp); err != nil {
			return err
		}

		// Verify their DH proof.
		if bytes.Equal(resp, theirDHproof) == false {
			return fmt.Errorf("Invalid DH proof %x", theirDHproof)
		}
	}

	theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
	copy(lnConn.RemoteLNId[:], theirAdr[:16])
	lnConn.RemotePub = theirPub
	lnConn.Authed = true

	return nil
}
Пример #7
0
// NewMixHeader creates a new mix header which is capable of obliviously
// routing a message through the mix-net path outline by 'paymentPath'
// to a final node indicated by 'identifier' housing a message addressed to
// 'dest'. This function returns the created mix header along with a derived
// shared secret for each node in the path.
func NewMixHeader(dest LightningAddress, identifier [securityParameter]byte,
	paymentPath []*btcec.PublicKey) (*MixHeader, [][sharedSecretSize]byte, error) {
	// Each hop performs ECDH with our ephemeral key pair to arrive at a
	// shared secret. Additionally, each hop randomizes the group element
	// for the next hop by multiplying it by the blinding factor. This way
	// we only need to transmit a single group element, and hops can't link
	// a session back to us if they have several nodes in the path.
	numHops := len(paymentPath)
	hopEphemeralPubKeys := make([]*btcec.PublicKey, numHops)
	hopSharedSecrets := make([][sha256.Size]byte, numHops)
	hopBlindingFactors := make([][sha256.Size]byte, numHops)

	// Generate a new ephemeral key to use for ECDH for this session.
	sessionKey, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return nil, nil, err
	}

	// Compute the triplet for the first hop outside of the main loop.
	// Within the loop each new triplet will be computed recursively based
	// off of the blinding factor of the last hop.
	hopEphemeralPubKeys[0] = sessionKey.PubKey()
	hopSharedSecrets[0] = sha256.Sum256(btcec.GenerateSharedSecret(sessionKey, paymentPath[0]))
	hopBlindingFactors[0] = computeBlindingFactor(hopEphemeralPubKeys[0], hopSharedSecrets[0][:])

	// Now recursively compute the ephemeral ECDH pub keys, the shared
	// secret, and blinding factor for each hop.
	for i := 1; i <= numHops-1; i++ {
		// a_{n} = a_{n-1} x c_{n-1} -> (Y_prev_pub_key x prevBlindingFactor)
		hopEphemeralPubKeys[i] = blindGroupElement(hopEphemeralPubKeys[i-1],
			hopBlindingFactors[i-1][:])

		// s_{n} = sha256( y_{n} x c_{n-1} ) ->
		// (Y_their_pub_key x x_our_priv) x all prev blinding factors
		yToX := blindGroupElement(paymentPath[i], sessionKey.D.Bytes())
		hopSharedSecrets[i] = sha256.Sum256(multiScalarMult(yToX, hopBlindingFactors[:i]).X.Bytes())

		// TODO(roasbeef): prob don't need to store all blinding factors, only the prev...
		// b_{n} = sha256(a_{n} || s_{n})
		hopBlindingFactors[i] = computeBlindingFactor(hopEphemeralPubKeys[i],
			hopSharedSecrets[i][:])

	}

	// Generate the padding, called "filler strings" in the paper.
	filler := generateHeaderPadding(numHops, hopSharedSecrets)

	// First we generate the routing info + MAC for the very last hop.
	mixHeader := make([]byte, 0, routingInfoSize)
	mixHeader = append(mixHeader, dest...)
	mixHeader = append(mixHeader, identifier[:]...)
	mixHeader = append(mixHeader,
		bytes.Repeat([]byte{0}, ((2*(numMaxHops-numHops)+2)*securityParameter-len(dest)))...)

	// Encrypt the header for the final hop with the shared secret the
	// destination will eventually derive, then pad the message out to full
	// size with the "random" filler bytes.
	streamBytes := generateCipherStream(generateKey("rho", hopSharedSecrets[numHops-1]), numStreamBytes)
	xor(mixHeader, mixHeader, streamBytes[:(2*(numMaxHops-numHops)+3)*securityParameter])
	mixHeader = append(mixHeader, filler...)

	// Calculate a MAC over the encrypted mix header for the last hop
	// (including the filler bytes), using the same shared secret key as
	// used for encryption above.
	headerMac := calcMac(generateKey("mu", hopSharedSecrets[numHops-1]), mixHeader)

	// Now we compute the routing information for each hop, along with a
	// MAC of the routing info using the shared key for that hop.
	for i := numHops - 2; i >= 0; i-- {
		// The next hop from the point of view of the current hop. Node
		// ID's are currently the hash160 of a node's pubKey serialized
		// in compressed format.
		nodeID := btcutil.Hash160(paymentPath[i+1].SerializeCompressed())

		var b bytes.Buffer
		b.Write(nodeID)
		// MAC for mix header.
		b.Write(headerMac[:])
		// Mix header itself.
		b.Write(mixHeader[:(2*numMaxHops-1)*securityParameter])

		streamBytes := generateCipherStream(generateKey("rho", hopSharedSecrets[i]), numStreamBytes)
		xor(mixHeader, b.Bytes(), streamBytes[:(2*numMaxHops+1)*securityParameter])
		headerMac = calcMac(generateKey("mu", hopSharedSecrets[i]), mixHeader)
	}

	var r [routingInfoSize]byte
	copy(r[:], mixHeader)
	header := &MixHeader{
		EphemeralKey: hopEphemeralPubKeys[0],
		RoutingInfo:  r,
		HeaderMAC:    headerMac,
	}

	return header, hopSharedSecrets, nil
}
Пример #8
0
// ProcessMixHeader...
// TODO(roasbeef): proto msg enum?
func (s *SphinxNode) ProcessForwardingMessage(fwdMsg *ForwardingMessage) (*processMsgAction, error) {
	mixHeader := fwdMsg.Header
	onionMsg := fwdMsg.Msg

	dhKey := mixHeader.EphemeralKey
	routeInfo := mixHeader.RoutingInfo
	headerMac := mixHeader.HeaderMAC

	// Ensure that the public key is on our curve.
	if !s.lnKey.Curve.IsOnCurve(dhKey.X, dhKey.Y) {
		return nil, fmt.Errorf("pubkey isn't on secp256k1 curve")
	}

	// Compute our shared secret.
	sharedSecret := sha256.Sum256(btcec.GenerateSharedSecret(s.lnKey, dhKey))

	// In order to mitigate replay attacks, if we've seen this particular
	// shared secret before, cease processing and just drop this forwarding
	// message.
	if _, ok := s.seenSecrets[sharedSecret]; ok {
		return nil, fmt.Errorf("shared secret previously seen")
	}

	// Using the derived shared secret, ensure the integrity of the routing
	// information by checking the attached MAC without leaking timing
	// information.
	calculatedMac := calcMac(generateKey("mu", sharedSecret), routeInfo[:])
	if !hmac.Equal(headerMac[:], calculatedMac[:]) {
		return nil, fmt.Errorf("MAC mismatch, rejecting forwarding message")
	}

	// The MAC checks out, mark this current shared secret as processed in
	// order to mitigate future replay attacks.
	s.seenSecrets[sharedSecret] = struct{}{}

	// Attach the padding zeroes in order to properly strip an encryption
	// layer off the routing info revealing the routing information for the
	// next hop.
	var hopInfo [numStreamBytes]byte
	streamBytes := generateCipherStream(generateKey("rho", sharedSecret), numStreamBytes)
	headerWithPadding := append(routeInfo[:], bytes.Repeat([]byte{0}, 2*securityParameter)...)
	xor(hopInfo[:], headerWithPadding, streamBytes)

	// Are we the final hop? Or should the message be forwarded further?
	switch hopInfo[0] {
	case nullDest: // We're the exit node for a forwarding message.
		onionCore := lionessDecode(generateKey("pi", sharedSecret), onionMsg)
		// TODO(roasbeef): check ver and reject if not our net.
		/*destAddr, _, _ := base58.CheckDecode(string(onionCore[securityParameter : securityParameter*2]))
		if err != nil {
			return nil, err
		}*/
		destAddr := onionCore[securityParameter : securityParameter*2]
		msg := onionCore[securityParameter*2:]
		return &processMsgAction{
			action:   ExitNode,
			destAddr: destAddr,
			destMsg:  msg,
		}, nil

	default: // The message is destined for another mix-net node.
		// TODO(roasbeef): prob extract to func

		// Randomize the DH group element for the next hop using the
		// deterministic blinding factor.
		blindingFactor := computeBlindingFactor(dhKey, sharedSecret[:])
		nextDHKey := blindGroupElement(dhKey, blindingFactor[:])

		// Parse out the ID of the next node in the route.
		var nextHop [securityParameter]byte
		copy(nextHop[:], hopInfo[:securityParameter])

		// MAC and MixHeader for the next hop.
		var nextMac [securityParameter]byte
		copy(nextMac[:], hopInfo[securityParameter:securityParameter*2])
		var nextMixHeader [routingInfoSize]byte
		copy(nextMixHeader[:], hopInfo[securityParameter*2:])

		// Strip a single layer of encryption from the onion for the
		// next hop to also process.
		nextOnion := lionessDecode(generateKey("pi", sharedSecret), onionMsg)

		nextFwdMsg := &ForwardingMessage{
			Header: &MixHeader{
				EphemeralKey: nextDHKey,
				RoutingInfo:  nextMixHeader,
				HeaderMAC:    nextMac,
			},
			Msg: nextOnion,
		}

		return &processMsgAction{
			action:  MoreHops,
			nextHop: nextHop,
			fwdMsg:  nextFwdMsg,
		}, nil
	}
}