@@ -11,7 +11,6 @@ import (
1111 "math"
1212 "math/big"
1313
14- "github.com/Crypt-iQ/lightning-onion/persistlog"
1514 "github.com/aead/chacha20"
1615 "github.com/lightningnetwork/lnd/chainntnfs"
1716 "github.com/roasbeef/btcd/btcec"
@@ -70,6 +69,10 @@ const (
7069 baseVersion = 0
7170)
7271
72+ // Hash256 is a statically sized, 32-byte array, typically containing
73+ // the output of a SHA256 hash.
74+ type Hash256 [sha256 .Size ]byte
75+
7376var (
7477 // paddingBytes are the padding bytes used to fill out the remainder of the
7578 // unused portion of the per-hop payload.
@@ -207,14 +210,14 @@ func (hd *HopData) Decode(r io.Reader) error {
207210// generateSharedSecrets by the given nodes pubkeys, generates the shared
208211// secrets.
209212func generateSharedSecrets (paymentPath []* btcec.PublicKey ,
210- sessionKey * btcec.PrivateKey ) [][ sha256 . Size ] byte {
213+ sessionKey * btcec.PrivateKey ) []Hash256 {
211214 // Each hop performs ECDH with our ephemeral key pair to arrive at a
212215 // shared secret. Additionally, each hop randomizes the group element
213216 // for the next hop by multiplying it by the blinding factor. This way
214217 // we only need to transmit a single group element, and hops can't link
215218 // a session back to us if they have several nodes in the path.
216219 numHops := len (paymentPath )
217- hopSharedSecrets := make ([][ sha256 . Size ] byte , numHops )
220+ hopSharedSecrets := make ([]Hash256 , numHops )
218221
219222 // Compute the triplet for the first hop outside of the main loop.
220223 // Within the loop each new triplet will be computed recursively based
@@ -301,8 +304,8 @@ func NewOnionPacket(paymentPath []*btcec.PublicKey, sessionKey *btcec.PrivateKey
301304 // We'll derive the two keys we need for each hop in order to:
302305 // generate our stream cipher bytes for the mixHeader, and
303306 // calculate the MAC over the entire constructed packet.
304- rhoKey := generateKey ("rho" , hopSharedSecrets [i ])
305- muKey := generateKey ("mu" , hopSharedSecrets [i ])
307+ rhoKey := generateKey ("rho" , & hopSharedSecrets [i ])
308+ muKey := generateKey ("mu" , & hopSharedSecrets [i ])
306309
307310 // The HMAC for the final hop is simply zeroes. This allows the
308311 // last hop to recognize that it is the destination for a
@@ -379,13 +382,13 @@ func rightShift(slice []byte, num int) {
379382// "filler" bytes produced by this function at the last hop. Using this
380383// methodology, the size of the field stays constant at each hop.
381384func generateHeaderPadding (key string , numHops int , hopSize int ,
382- sharedSecrets [][ sharedSecretSize ] byte ) []byte {
385+ sharedSecrets []Hash256 ) []byte {
383386
384387 filler := make ([]byte , (numHops - 1 )* hopSize )
385388 for i := 1 ; i < numHops ; i ++ {
386389 totalFillerSize := ((NumMaxHops - i ) + 1 ) * hopSize
387390
388- streamKey := generateKey (key , sharedSecrets [i - 1 ])
391+ streamKey := generateKey (key , & sharedSecrets [i - 1 ])
389392 streamBytes := generateCipherStream (streamKey , numStreamBytes )
390393
391394 xor (filler , filler , streamBytes [totalFillerSize :totalFillerSize + i * hopSize ])
@@ -486,7 +489,7 @@ func xor(dst, a, b []byte) int {
486489// construction/processing based off of the denoted keyType. Within Sphinx
487490// various keys are used within the same onion packet for padding generation,
488491// MAC generation, and encryption/decryption.
489- func generateKey (keyType string , sharedKey [ sharedSecretSize ] byte ) [keyLen ]byte {
492+ func generateKey (keyType string , sharedKey * Hash256 ) [keyLen ]byte {
490493 mac := hmac .New (sha256 .New , []byte (keyType ))
491494 mac .Write (sharedKey [:])
492495 h := mac .Sum (nil )
@@ -517,12 +520,14 @@ func generateCipherStream(key [keyLen]byte, numBytes uint) []byte {
517520// computeBlindingFactor for the next hop given the ephemeral pubKey and
518521// sharedSecret for this hop. The blinding factor is computed as the
519522// sha-256(pubkey || sharedSecret).
520- func computeBlindingFactor (hopPubKey * btcec.PublicKey , hopSharedSecret []byte ) [sha256 .Size ]byte {
523+ func computeBlindingFactor (hopPubKey * btcec.PublicKey ,
524+ hopSharedSecret []byte ) Hash256 {
525+
521526 sha := sha256 .New ()
522527 sha .Write (hopPubKey .SerializeCompressed ())
523528 sha .Write (hopSharedSecret )
524529
525- var hash [ sha256 . Size ] byte
530+ var hash Hash256
526531 copy (hash [:], sha .Sum (nil ))
527532 return hash
528533}
@@ -547,7 +552,7 @@ func blindBaseElement(blindingFactor []byte) *btcec.PublicKey {
547552// key. We then take the _entire_ point generated by the ECDH operation,
548553// serialize that using a compressed format, then feed the raw bytes through a
549554// single SHA256 invocation. The resulting value is the shared secret.
550- func generateSharedSecret (pub * btcec.PublicKey , priv * btcec.PrivateKey ) [ 32 ] byte {
555+ func generateSharedSecret (pub * btcec.PublicKey , priv * btcec.PrivateKey ) Hash256 {
551556 s := & btcec.PublicKey {}
552557 x , y := btcec .S256 ().ScalarMult (pub .X , pub .Y , priv .D .Bytes ())
553558 s .X = x
@@ -622,23 +627,19 @@ type Router struct {
622627
623628 onionKey * btcec.PrivateKey
624629
625- d * persistlog. DecayedLog
630+ log ReplayLog
626631}
627632
628633// NewRouter creates a new instance of a Sphinx onion Router given the node's
629634// currently advertised onion private key, and the target Bitcoin network.
630- func NewRouter (nodeKey * btcec.PrivateKey , net * chaincfg.Params ,
635+ func NewRouter (dbPath string , nodeKey * btcec.PrivateKey , net * chaincfg.Params ,
631636 chainNotifier chainntnfs.ChainNotifier ) * Router {
632637 var nodeID [addressSize ]byte
633638 copy (nodeID [:], btcutil .Hash160 (nodeKey .PubKey ().SerializeCompressed ()))
634639
635640 // Safe to ignore the error here, nodeID is 20 bytes.
636641 nodeAddr , _ := btcutil .NewAddressPubKeyHash (nodeID [:], net )
637642
638- d := & persistlog.DecayedLog {
639- Notifier : chainNotifier ,
640- }
641-
642643 return & Router {
643644 nodeID : nodeID ,
644645 nodeAddr : nodeAddr ,
@@ -652,20 +653,20 @@ func NewRouter(nodeKey *btcec.PrivateKey, net *chaincfg.Params,
652653 },
653654 // TODO(roasbeef): replace instead with bloom filter?
654655 // * https://moderncrypto.org/mail-archive/messaging/2015/001911.html
655- d : d ,
656+ log : NewDecayedLog ( dbPath , chainNotifier ) ,
656657 }
657658}
658659
659660// Start starts / opens the DecayedLog's channeldb and its accompanying
660661// garbage collector goroutine.
661662func (r * Router ) Start () error {
662- return r .d .Start ("" )
663+ return r .log .Start ()
663664}
664665
665666// Stop stops / closes the DecayedLog's channeldb and its accompanying
666667// garbage collector goroutine.
667668func (r * Router ) Stop () {
668- r .d .Stop ()
669+ r .log .Stop ()
669670}
670671
671672// ProcessOnionPacket processes an incoming onion packet which has been forward
@@ -677,28 +678,46 @@ func (r *Router) Stop() {
677678// In the case of a successful packet processing, and ProcessedPacket struct is
678679// returned which houses the newly parsed packet, along with instructions on
679680// what to do next.
680- func (r * Router ) ProcessOnionPacket (onionPkt * OnionPacket , assocData []byte ) (* ProcessedPacket , error ) {
681- dhKey := onionPkt .EphemeralKey
682- routeInfo := onionPkt .RoutingInfo
683- headerMac := onionPkt .HeaderMAC
681+ func (r * Router ) ProcessOnionPacket (onionPkt * OnionPacket ,
682+ assocData []byte , incomingCltv uint32 ) (* ProcessedPacket , error ) {
684683
684+ // Compute the shared secret for this onion packet.
685685 sharedSecret , err := r .generateSharedSecret (onionPkt .EphemeralKey )
686686 if err != nil {
687687 return nil , err
688688 }
689689
690- // In order to mitigate replay attacks, if we've seen this particular
691- // shared secret before, cease processing and just drop this forwarding
692- // message.
693- hashedSecret := persistlog .HashSharedSecret (sharedSecret )
694- cltv , err := r .d .Get (hashedSecret [:])
690+ // Additionally, compute the hash prefix of the shared secret, which
691+ // will serve as an identifier for detecting replayed packets.
692+ hashPrefix := hashSharedSecret (& sharedSecret )
693+
694+ // Continue to optimistically process this packet, deferring replay
695+ // protection until the end to reduce the penalty of multiple IO
696+ // operations.
697+ packet , err := processOnionPacket (onionPkt , & sharedSecret , assocData )
695698 if err != nil {
696699 return nil , err
697700 }
698- if cltv != math .MaxUint32 {
699- return nil , ErrReplayedPacket
701+
702+ // Atomically compare this hash prefix with the contents of the on-disk
703+ // log, persisting it only if this entry was not detected as a replay.
704+ if err := r .log .Put (& hashPrefix , incomingCltv ); err != nil {
705+ return nil , err
700706 }
701707
708+ return packet , nil
709+ }
710+
711+ // processOnionPacket performs the primary key derivation and handling of onion
712+ // packets. The processed packets returned from this method should only be used
713+ // if the packet was not flagged as a replayed packet.
714+ func processOnionPacket (onionPkt * OnionPacket ,
715+ sharedSecret * Hash256 , assocData []byte ) (* ProcessedPacket , error ) {
716+
717+ dhKey := onionPkt .EphemeralKey
718+ routeInfo := onionPkt .RoutingInfo
719+ headerMac := onionPkt .HeaderMAC
720+
702721 // Using the derived shared secret, ensure the integrity of the routing
703722 // information by checking the attached MAC without leaking timing
704723 // information.
@@ -711,9 +730,14 @@ func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, assocData []byte) (*P
711730 // Attach the padding zeroes in order to properly strip an encryption
712731 // layer off the routing info revealing the routing information for the
713732 // next hop.
733+ streamBytes := generateCipherStream (
734+ generateKey ("rho" , sharedSecret ),
735+ numStreamBytes ,
736+ )
737+ headerWithPadding := append (routeInfo [:],
738+ bytes .Repeat ([]byte {0 }, hopDataSize )... )
739+
714740 var hopInfo [numStreamBytes ]byte
715- streamBytes := generateCipherStream (generateKey ("rho" , sharedSecret ), numStreamBytes )
716- headerWithPadding := append (routeInfo [:], bytes .Repeat ([]byte {0 }, hopDataSize )... )
717741 xor (hopInfo [:], headerWithPadding , streamBytes )
718742
719743 // Randomize the DH group element for the next hop using the
@@ -729,23 +753,6 @@ func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, assocData []byte) (*P
729753 return nil , err
730754 }
731755
732- // The MAC checks out, mark this current shared secret as processed in
733- // order to mitigate future replay attacks. We need to check to see if
734- // we already know the secret again since a replay might have happened
735- // while we were checking the MAC and decoding the HopData.
736- cltv , err = r .d .Get (hashedSecret [:])
737- if err != nil {
738- return nil , err
739- }
740- if cltv != math .MaxUint32 {
741- return nil , ErrReplayedPacket
742- }
743-
744- err = r .d .Put (hashedSecret [:], hopData .OutgoingCltv )
745- if err != nil {
746- return nil , err
747- }
748-
749756 // With the necessary items extracted, we'll copy of the onion packet
750757 // for the next node, snipping off our per-hop data.
751758 var nextMixHeader [routingInfoSize ]byte
@@ -761,7 +768,7 @@ func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, assocData []byte) (*P
761768 // However if the uncovered 'nextMac' is all zeroes, then this
762769 // indicates that we're the final hop in the route.
763770 var action ProcessCode = MoreHops
764- if bytes .Compare (bytes . Repeat ([] byte { 0x00 }, hmacSize ) , hopData .HMAC [:]) == 0 {
771+ if bytes .Compare (zeroHMAC [:] , hopData .HMAC [:]) == 0 {
765772 action = ExitNode
766773 }
767774
@@ -773,9 +780,9 @@ func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, assocData []byte) (*P
773780}
774781
775782// generateSharedSecret generates the shared secret by given ephemeral key.
776- func (r * Router ) generateSharedSecret (dhKey * btcec.PublicKey ) ([ sha256 . Size ] byte ,
783+ func (r * Router ) generateSharedSecret (dhKey * btcec.PublicKey ) (Hash256 ,
777784 error ) {
778- var sharedSecret [ sha256 . Size ] byte
785+ var sharedSecret Hash256
779786
780787 // Ensure that the public key is on our curve.
781788 if ! btcec .S256 ().IsOnCurve (dhKey .X , dhKey .Y ) {
@@ -786,3 +793,96 @@ func (r *Router) generateSharedSecret(dhKey *btcec.PublicKey) ([sha256.Size]byte
786793 sharedSecret = generateSharedSecret (dhKey , r .onionKey )
787794 return sharedSecret , nil
788795}
796+
797+ // Tx is a transaction consisting of a number of sphinx packets to be atomically
798+ // written to the replay log. This structure helps to coordinate construction of
799+ // the underlying Batch object, and to ensure that the result of the processing
800+ // is idempotent.
801+ type Tx struct {
802+ // batch is the set of packets to be incrementally processed and
803+ // ultimately committed in this transaction
804+ batch * Batch
805+
806+ // router is a reference to the sphinx router that created this
807+ // transaction. Committing this transaction will utilize this router's
808+ // replay log.
809+ router * Router
810+
811+ // packets contains a potentially sparse list of optimistically processed
812+ // packets for this batch. The contents of a particular index should
813+ // only be accessed if the index is *not* included in the replay set, or
814+ // otherwise failed any other stage of the processing.
815+ packets []ProcessedPacket
816+ }
817+
818+ // BeginTxn creates a new transaction that can later be committed back to the
819+ // sphinx router's replay log.
820+ //
821+ // NOTE: The nels parameter should represent the maximum number of that could be
822+ // added to the batch, using sequence numbers that match or exceed this value
823+ // could result in an out-of-bounds panic.
824+ func (r * Router ) BeginTxn (id []byte , nels int ) * Tx {
825+ return & Tx {
826+ batch : NewBatch (id ),
827+ router : r ,
828+ packets : make ([]ProcessedPacket , nels ),
829+ }
830+ }
831+
832+ // ProcessOnionPacket processes an incoming onion packet which has been forward
833+ // to the target Sphinx router. If the encoded ephemeral key isn't on the
834+ // target Elliptic Curve, then the packet is rejected. Similarly, if the
835+ // derived shared secret has been seen before the packet is rejected. Finally
836+ // if the MAC doesn't check the packet is again rejected.
837+ //
838+ // In the case of a successful packet processing, and ProcessedPacket struct is
839+ // returned which houses the newly parsed packet, along with instructions on
840+ // what to do next.
841+ func (t * Tx ) ProcessOnionPacket (seqNum uint16 , onionPkt * OnionPacket ,
842+ assocData []byte , incomingCltv uint32 ) error {
843+
844+ // Compute the shared secret for this onion packet.
845+ sharedSecret , err := t .router .generateSharedSecret (
846+ onionPkt .EphemeralKey )
847+ if err != nil {
848+ return err
849+ }
850+
851+ // Additionally, compute the hash prefix of the shared secret, which
852+ // will serve as an identifier for detecting replayed packets.
853+ hashPrefix := hashSharedSecret (& sharedSecret )
854+
855+ // Continue to optimistically process this packet, deferring replay
856+ // protection until the end to reduce the penalty of multiple IO
857+ // operations.
858+ packet , err := processOnionPacket (onionPkt , & sharedSecret , assocData )
859+ if err != nil {
860+ return err
861+ }
862+
863+ // Add the hash prefix to pending batch of shared secrets that will be
864+ // written later via Commit().
865+ err = t .batch .Put (seqNum , & hashPrefix , incomingCltv )
866+ if err != nil {
867+ return err
868+ }
869+
870+ // If we successfully added this packet to the batch, cache the processed
871+ // packet within the Tx which can be accessed after committing if this
872+ // sequence number does not appear in the replay set.
873+ t .packets [seqNum ] = * packet
874+
875+ return nil
876+ }
877+
878+ // Commit writes this transaction's batch of sphinx packets to the replay log,
879+ // performing a final check against the log for replays.
880+ func (t * Tx ) Commit () ([]ProcessedPacket , * ReplaySet , error ) {
881+ if t .batch .isCommitted {
882+ return t .packets , t .batch .replaySet , nil
883+ }
884+
885+ rs , err := t .router .log .PutBatch (t .batch )
886+
887+ return t .packets , rs , err
888+ }
0 commit comments