]> git.ipfire.org Git - thirdparty/wireguard-go.git/commitdiff
Restructuring of noise impl.
authorMathias Hall-Andersen <mathias@hall-andersen.dk>
Sat, 24 Jun 2017 13:34:17 +0000 (15:34 +0200)
committerMathias Hall-Andersen <mathias@hall-andersen.dk>
Sat, 24 Jun 2017 13:34:44 +0000 (15:34 +0200)
src/config.go
src/device.go
src/index.go [new file with mode: 0644]
src/keypair.go [new file with mode: 0644]
src/noise_helpers.go
src/noise_protocol.go
src/noise_test.go
src/peer.go
src/routing.go
src/tai64.go

index a61b94055c0bc2ee32c6f58e37b72be643895921..88651944133f2054314728cdb3ee4c8e68aa956f 100644 (file)
@@ -99,11 +99,7 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
                        if ok {
                                peer = found
                        } else {
-                               newPeer := &Peer{
-                                       publicKey: pubKey,
-                               }
-                               peer = newPeer
-                               device.peers[pubKey] = newPeer
+                               peer = device.NewPeer(pubKey)
                        }
 
                case "replace_peers":
@@ -125,14 +121,14 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
 
                        case "remove":
                                peer.mutex.Lock()
-                               device.RemovePeer(peer.publicKey)
+                               // device.RemovePeer(peer.publicKey)
                                peer = nil
 
                        case "preshared_key":
                                err := func() error {
                                        peer.mutex.Lock()
                                        defer peer.mutex.Unlock()
-                                       return peer.presharedKey.FromHex(value)
+                                       return peer.handshake.presharedKey.FromHex(value)
                                }()
                                if err != nil {
                                        return &IPCError{Code: ipcErrorInvalidPublicKey}
@@ -144,7 +140,7 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
                                        return &IPCError{Code: ipcErrorInvalidIPAddress}
                                }
                                peer.mutex.Lock()
-                               peer.endpoint = ip
+                               // peer.endpoint = ip FIX
                                peer.mutex.Unlock()
 
                        case "persistent_keepalive_interval":
index 9f1daa6295a15befa03469b4893c26a828fa2233..996903421614ef1b53bf5182d09ac5815366f3b8 100644 (file)
@@ -1,17 +1,13 @@
 package main
 
 import (
-       "math/rand"
        "sync"
 )
 
-/* TODO: Locking may be a little broad here
- */
-
 type Device struct {
        mutex        sync.RWMutex
        peers        map[NoisePublicKey]*Peer
-       sessions     map[uint32]*Handshake
+       indices      IndexTable
        privateKey   NoisePrivateKey
        publicKey    NoisePublicKey
        fwMark       uint32
@@ -19,43 +15,66 @@ type Device struct {
        routingTable RoutingTable
 }
 
-func (dev *Device) NewID(h *Handshake) uint32 {
-       dev.mutex.Lock()
-       defer dev.mutex.Unlock()
-       for {
-               id := rand.Uint32()
-               _, ok := dev.sessions[id]
-               if !ok {
-                       dev.sessions[id] = h
-                       return id
-               }
+func (device *Device) SetPrivateKey(sk NoisePrivateKey) {
+       device.mutex.Lock()
+       defer device.mutex.Unlock()
+
+       // update key material
+
+       device.privateKey = sk
+       device.publicKey = sk.publicKey()
+
+       // do precomputations
+
+       for _, peer := range device.peers {
+               h := &peer.handshake
+               h.mutex.Lock()
+               h.precomputedStaticStatic = device.privateKey.sharedSecret(h.remoteStatic)
+               h.mutex.Unlock()
        }
 }
 
-func (dev *Device) RemovePeer(key NoisePublicKey) {
-       dev.mutex.Lock()
-       defer dev.mutex.Unlock()
-       peer, ok := dev.peers[key]
+func (device *Device) Init() {
+       device.mutex.Lock()
+       defer device.mutex.Unlock()
+
+       device.peers = make(map[NoisePublicKey]*Peer)
+       device.indices.Init()
+       device.listenPort = 0
+       device.routingTable.Reset()
+}
+
+func (device *Device) LookupPeer(pk NoisePublicKey) *Peer {
+       device.mutex.RLock()
+       defer device.mutex.RUnlock()
+       return device.peers[pk]
+}
+
+func (device *Device) RemovePeer(key NoisePublicKey) {
+       device.mutex.Lock()
+       defer device.mutex.Unlock()
+
+       peer, ok := device.peers[key]
        if !ok {
                return
        }
        peer.mutex.Lock()
-       dev.routingTable.RemovePeer(peer)
-       delete(dev.peers, key)
+       device.routingTable.RemovePeer(peer)
+       delete(device.peers, key)
 }
 
-func (dev *Device) RemoveAllAllowedIps(peer *Peer) {
+func (device *Device) RemoveAllAllowedIps(peer *Peer) {
 
 }
 
-func (dev *Device) RemoveAllPeers() {
-       dev.mutex.Lock()
-       defer dev.mutex.Unlock()
+func (device *Device) RemoveAllPeers() {
+       device.mutex.Lock()
+       defer device.mutex.Unlock()
 
-       for key, peer := range dev.peers {
+       for key, peer := range device.peers {
                peer.mutex.Lock()
-               dev.routingTable.RemovePeer(peer)
-               delete(dev.peers, key)
+               device.routingTable.RemovePeer(peer)
+               delete(device.peers, key)
                peer.mutex.Unlock()
        }
 }
diff --git a/src/index.go b/src/index.go
new file mode 100644 (file)
index 0000000..83a7e29
--- /dev/null
@@ -0,0 +1,82 @@
+package main
+
+import (
+       "crypto/rand"
+       "sync"
+)
+
+/* Index=0 is reserved for unset indecies
+ *
+ */
+
+type IndexTable struct {
+       mutex      sync.RWMutex
+       keypairs   map[uint32]*KeyPair
+       handshakes map[uint32]*Handshake
+}
+
+func randUint32() (uint32, error) {
+       var buff [4]byte
+       _, err := rand.Read(buff[:])
+       id := uint32(buff[0])
+       id <<= 8
+       id |= uint32(buff[1])
+       id <<= 8
+       id |= uint32(buff[2])
+       id <<= 8
+       id |= uint32(buff[3])
+       return id, err
+}
+
+func (table *IndexTable) Init() {
+       table.mutex.Lock()
+       defer table.mutex.Unlock()
+       table.keypairs = make(map[uint32]*KeyPair)
+       table.handshakes = make(map[uint32]*Handshake)
+}
+
+func (table *IndexTable) NewIndex(handshake *Handshake) (uint32, error) {
+       table.mutex.Lock()
+       defer table.mutex.Unlock()
+       for {
+               // generate random index
+
+               id, err := randUint32()
+               if err != nil {
+                       return id, err
+               }
+               if id == 0 {
+                       continue
+               }
+
+               // check if index used
+
+               _, ok := table.keypairs[id]
+               if ok {
+                       continue
+               }
+               _, ok = table.handshakes[id]
+               if ok {
+                       continue
+               }
+
+               // update the index
+
+               delete(table.handshakes, handshake.localIndex)
+               handshake.localIndex = id
+               table.handshakes[id] = handshake
+               return id, nil
+       }
+}
+
+func (table *IndexTable) LookupKeyPair(id uint32) *KeyPair {
+       table.mutex.RLock()
+       defer table.mutex.RUnlock()
+       return table.keypairs[id]
+}
+
+func (table *IndexTable) LookupHandshake(id uint32) *Handshake {
+       table.mutex.RLock()
+       defer table.mutex.RUnlock()
+       return table.handshakes[id]
+}
diff --git a/src/keypair.go b/src/keypair.go
new file mode 100644 (file)
index 0000000..22a8244
--- /dev/null
@@ -0,0 +1,12 @@
+package main
+
+import (
+       "crypto/cipher"
+)
+
+type KeyPair struct {
+       recieveKey   cipher.AEAD
+       recieveNonce NoiseNonce
+       sendKey      cipher.AEAD
+       sendNonce    NoiseNonce
+}
index df2501109d4b21e94aa84a6bd5bf91eea4b687e4..eadbc07b0e663870fd1f98efc2e6b56d0d797b81 100644 (file)
@@ -81,6 +81,6 @@ func (sk *NoisePrivateKey) publicKey() (pk NoisePublicKey) {
 func (sk *NoisePrivateKey) sharedSecret(pk NoisePublicKey) (ss [NoisePublicKeySize]byte) {
        apk := (*[NoisePublicKeySize]byte)(&pk)
        ask := (*[NoisePrivateKeySize]byte)(sk)
-       curve25519.ScalarMult(&ss, apk, ask)
+       curve25519.ScalarMult(&ss, ask, apk)
        return ss
 }
index e7c87745154561f9428ba952a036c0d71c22450e..b9c8981e79c293c054df93ceb8b367aad2c46501 100644 (file)
@@ -56,18 +56,22 @@ type MessageTransport struct {
 }
 
 type Handshake struct {
-       lock         sync.Mutex
-       state        int
-       chainKey     [blake2s.Size]byte // chain key
-       hash         [blake2s.Size]byte // hash value
-       staticStatic NoisePublicKey     // precomputed DH(S_i, S_r)
-       ephemeral    NoisePrivateKey    // ephemeral secret key
-       remoteIndex  uint32             // index for sending
-       device       *Device
-       peer         *Peer
+       state                   int
+       mutex                   sync.Mutex
+       hash                    [blake2s.Size]byte       // hash value
+       chainKey                [blake2s.Size]byte       // chain key
+       presharedKey            NoiseSymmetricKey        // psk
+       localEphemeral          NoisePrivateKey          // ephemeral secret key
+       localIndex              uint32                   // used to clear hash-table
+       remoteIndex             uint32                   // index for sending
+       remoteStatic            NoisePublicKey           // long term key
+       remoteEphemeral         NoisePublicKey           // ephemeral public key
+       precomputedStaticStatic [NoisePublicKeySize]byte // precomputed shared secret
+       lastTimestamp           TAI64N
 }
 
 var (
+       EmptyMessage   []byte
        ZeroNonce      [chacha20poly1305.NonceSize]byte
        InitalChainKey [blake2s.Size]byte
        InitalHash     [blake2s.Size]byte
@@ -78,102 +82,196 @@ func init() {
        InitalHash = blake2s.Sum256(append(InitalChainKey[:], []byte(WGIdentifier)...))
 }
 
-func (h *Handshake) Precompute() {
-       h.staticStatic = h.device.privateKey.sharedSecret(h.peer.publicKey)
-}
-
-func (h *Handshake) ConsumeMessageResponse(msg *MessageResponse) {
-
-}
-
-func (h *Handshake) addHash(data []byte) {
+func (h *Handshake) addToHash(data []byte) {
        h.hash = addToHash(h.hash, data)
 }
 
-func (h *Handshake) addChain(data []byte) {
+func (h *Handshake) addToChainKey(data []byte) {
        h.chainKey = addToChainKey(h.chainKey, data)
 }
 
-func (h *Handshake) CreateMessageInital() (*MessageInital, error) {
-       h.lock.Lock()
-       defer h.lock.Unlock()
+func (device *Device) Precompute(peer *Peer) {
+       h := &peer.handshake
+       h.precomputedStaticStatic = device.privateKey.sharedSecret(h.remoteStatic)
+}
+
+func (device *Device) CreateMessageInitial(peer *Peer) (*MessageInital, error) {
+       handshake := &peer.handshake
+       handshake.mutex.Lock()
+       defer handshake.mutex.Unlock()
 
-       // reset handshake
+       // create ephemeral key
 
        var err error
-       h.ephemeral, err = newPrivateKey()
+       handshake.chainKey = InitalChainKey
+       handshake.hash = addToHash(InitalHash, handshake.remoteStatic[:])
+       handshake.localEphemeral, err = newPrivateKey()
        if err != nil {
                return nil, err
        }
-       h.chainKey = InitalChainKey
-       h.hash = addToHash(InitalHash, h.device.publicKey[:])
 
-       // create ephemeral key
+       // assign index
 
        var msg MessageInital
+
        msg.Type = MessageInitalType
-       msg.Sender = h.device.NewID(h)
-       msg.Ephemeral = h.ephemeral.publicKey()
-       h.chainKey = addToChainKey(h.chainKey, msg.Ephemeral[:])
-       h.hash = addToHash(h.hash, msg.Ephemeral[:])
+       msg.Ephemeral = handshake.localEphemeral.publicKey()
+       msg.Sender, err = device.indices.NewIndex(handshake)
+
+       if err != nil {
+               return nil, err
+       }
+
+       handshake.addToChainKey(msg.Ephemeral[:])
+       handshake.addToHash(msg.Ephemeral[:])
 
        // encrypt long-term "identity key"
 
        func() {
                var key [chacha20poly1305.KeySize]byte
-               ss := h.ephemeral.sharedSecret(h.peer.publicKey)
-               h.chainKey, key = KDF2(h.chainKey[:], ss[:])
+               ss := handshake.localEphemeral.sharedSecret(handshake.remoteStatic)
+               handshake.chainKey, key = KDF2(handshake.chainKey[:], ss[:])
                aead, _ := chacha20poly1305.New(key[:])
-               aead.Seal(msg.Static[:0], ZeroNonce[:], h.device.publicKey[:], nil)
+               aead.Seal(msg.Static[:0], ZeroNonce[:], device.publicKey[:], handshake.hash[:])
        }()
-       h.addHash(msg.Static[:])
+       handshake.addToHash(msg.Static[:])
 
        // encrypt timestamp
 
        timestamp := Timestamp()
        func() {
                var key [chacha20poly1305.KeySize]byte
-               h.chainKey, key = KDF2(h.chainKey[:], h.staticStatic[:])
+               handshake.chainKey, key = KDF2(
+                       handshake.chainKey[:],
+                       handshake.precomputedStaticStatic[:],
+               )
                aead, _ := chacha20poly1305.New(key[:])
-               aead.Seal(msg.Timestamp[:0], ZeroNonce[:], timestamp[:], nil)
+               aead.Seal(msg.Timestamp[:0], ZeroNonce[:], timestamp[:], handshake.hash[:])
        }()
-       h.addHash(msg.Timestamp[:])
-       h.state = HandshakeInitialCreated
+
+       handshake.addToHash(msg.Timestamp[:])
+       handshake.state = HandshakeInitialCreated
+
        return &msg, nil
 }
 
-func (h *Handshake) ConsumeMessageInitial(msg *MessageInital) error {
+func (device *Device) ConsumeMessageInitial(msg *MessageInital) *Peer {
        if msg.Type != MessageInitalType {
                panic(errors.New("bug: invalid inital message type"))
        }
 
-       hash := addToHash(InitalHash, h.device.publicKey[:])
-       chainKey := addToChainKey(InitalChainKey, msg.Ephemeral[:])
+       hash := addToHash(InitalHash, device.publicKey[:])
        hash = addToHash(hash, msg.Ephemeral[:])
+       chainKey := addToChainKey(InitalChainKey, msg.Ephemeral[:])
 
-       //
+       // decrypt identity key
 
-       ephemeral, err := newPrivateKey()
+       var err error
+       var peerPK NoisePublicKey
+       func() {
+               var key [chacha20poly1305.KeySize]byte
+               ss := device.privateKey.sharedSecret(msg.Ephemeral)
+               chainKey, key = KDF2(chainKey[:], ss[:])
+               aead, _ := chacha20poly1305.New(key[:])
+               _, err = aead.Open(peerPK[:0], ZeroNonce[:], msg.Static[:], hash[:])
+       }()
        if err != nil {
-               return err
+               return nil
        }
+       hash = addToHash(hash, msg.Static[:])
 
-       // update handshake state
+       // find peer
+
+       peer := device.LookupPeer(peerPK)
+       if peer == nil {
+               return nil
+       }
+       handshake := &peer.handshake
+       handshake.mutex.Lock()
+       defer handshake.mutex.Unlock()
+
+       // decrypt timestamp
+
+       var timestamp TAI64N
+       func() {
+               var key [chacha20poly1305.KeySize]byte
+               chainKey, key = KDF2(
+                       chainKey[:],
+                       handshake.precomputedStaticStatic[:],
+               )
+               aead, _ := chacha20poly1305.New(key[:])
+               _, err = aead.Open(timestamp[:0], ZeroNonce[:], msg.Timestamp[:], hash[:])
+       }()
+       if err != nil {
+               return nil
+       }
+       hash = addToHash(hash, msg.Timestamp[:])
 
-       h.lock.Lock()
-       defer h.lock.Unlock()
+       // check for replay attack
 
-       h.hash = hash
-       h.chainKey = chainKey
-       h.remoteIndex = msg.Sender
-       h.ephemeral = ephemeral
-       h.state = HandshakeInitialConsumed
+       if !timestamp.After(handshake.lastTimestamp) {
+               return nil
+       }
+
+       // check for flood attack
 
-       return nil
+       // update handshake state
 
+       handshake.hash = hash
+       handshake.chainKey = chainKey
+       handshake.remoteIndex = msg.Sender
+       handshake.remoteEphemeral = msg.Ephemeral
+       handshake.state = HandshakeInitialConsumed
+       return peer
 }
 
-func (h *Handshake) CreateMessageResponse() []byte {
+func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error) {
+       handshake := &peer.handshake
+       handshake.mutex.Lock()
+       defer handshake.mutex.Unlock()
+
+       if handshake.state != HandshakeInitialConsumed {
+               panic(errors.New("bug: handshake initation must be consumed first"))
+       }
+
+       // assign index
+
+       var err error
+       var msg MessageResponse
+       msg.Type = MessageResponseType
+       msg.Sender, err = device.indices.NewIndex(handshake)
+       msg.Reciever = handshake.remoteIndex
+       if err != nil {
+               return nil, err
+       }
 
-       return nil
+       // create ephemeral key
+
+       handshake.localEphemeral, err = newPrivateKey()
+       if err != nil {
+               return nil, err
+       }
+       msg.Ephemeral = handshake.localEphemeral.publicKey()
+
+       func() {
+               ss := handshake.localEphemeral.sharedSecret(handshake.remoteEphemeral)
+               handshake.addToChainKey(ss[:])
+               ss = handshake.localEphemeral.sharedSecret(handshake.remoteStatic)
+               handshake.addToChainKey(ss[:])
+       }()
+
+       // add preshared key (psk)
+
+       var tau [blake2s.Size]byte
+       var key [chacha20poly1305.KeySize]byte
+       handshake.chainKey, tau, key = KDF3(handshake.chainKey[:], handshake.presharedKey[:])
+       handshake.addToHash(tau[:])
+
+       func() {
+               aead, _ := chacha20poly1305.New(key[:])
+               aead.Seal(msg.Empty[:0], ZeroNonce[:], EmptyMessage, handshake.hash[:])
+               handshake.addToHash(msg.Empty[:])
+       }()
+
+       return &msg, nil
 }
index b3ea54fbf7234214d32414e235a42f1f3d67b387..8d6a0fa159252762492ad4eb878d1f8f0754eadb 100644 (file)
@@ -1,38 +1,93 @@
 package main
 
 import (
+       "bytes"
+       "encoding/binary"
        "testing"
 )
 
-func TestHandshake(t *testing.T) {
-       var dev1 Device
-       var dev2 Device
-
-       var err error
-
-       dev1.privateKey, err = newPrivateKey()
+func assertNil(t *testing.T, err error) {
        if err != nil {
                t.Fatal(err)
        }
+}
+
+func assertEqual(t *testing.T, a []byte, b []byte) {
+       if bytes.Compare(a, b) != 0 {
+               t.Fatal(a, "!=", b)
+       }
+}
 
-       dev2.privateKey, err = newPrivateKey()
+func TestCurveWrappers(t *testing.T) {
+       sk1, err := newPrivateKey()
+       assertNil(t, err)
+
+       sk2, err := newPrivateKey()
+       assertNil(t, err)
+
+       pk1 := sk1.publicKey()
+       pk2 := sk2.publicKey()
+
+       ss1 := sk1.sharedSecret(pk2)
+       ss2 := sk2.sharedSecret(pk1)
+
+       if ss1 != ss2 {
+               t.Fatal("Failed to compute shared secet")
+       }
+}
+
+func newDevice(t *testing.T) *Device {
+       var device Device
+       sk, err := newPrivateKey()
        if err != nil {
                t.Fatal(err)
        }
+       device.Init()
+       device.SetPrivateKey(sk)
+       return &device
+}
+
+func TestNoiseHandshake(t *testing.T) {
+
+       dev1 := newDevice(t)
+       dev2 := newDevice(t)
 
-       var peer1 Peer
-       var peer2 Peer
+       peer1 := dev2.NewPeer(dev1.privateKey.publicKey())
+       peer2 := dev1.NewPeer(dev2.privateKey.publicKey())
 
-       peer1.publicKey = dev1.privateKey.publicKey()
-       peer2.publicKey = dev2.privateKey.publicKey()
+       assertEqual(
+               t,
+               peer1.handshake.precomputedStaticStatic[:],
+               peer2.handshake.precomputedStaticStatic[:],
+       )
+
+       /* simulate handshake */
+
+       // Initiation message
+
+       msg1, err := dev1.CreateMessageInitial(peer2)
+       assertNil(t, err)
+
+       packet := make([]byte, 0, 256)
+       writer := bytes.NewBuffer(packet)
+       err = binary.Write(writer, binary.LittleEndian, msg1)
+       peer := dev2.ConsumeMessageInitial(msg1)
+       if peer == nil {
+               t.Fatal("handshake failed at initiation message")
+       }
 
-       var handshake1 Handshake
-       var handshake2 Handshake
+       assertEqual(
+               t,
+               peer1.handshake.chainKey[:],
+               peer2.handshake.chainKey[:],
+       )
 
-       handshake1.device = &dev1
-       handshake2.device = &dev2
+       assertEqual(
+               t,
+               peer1.handshake.hash[:],
+               peer2.handshake.hash[:],
+       )
 
-       handshake1.peer = &peer2
-       handshake2.peer = &peer1
+       // Response message
 
 }
index db5e99f52ba27afb7481d8ca175472696b4a9aa1..f6eb555ff6ade422198267698257a47b70039b71 100644 (file)
@@ -6,17 +6,35 @@ import (
        "time"
 )
 
-type KeyPair struct {
-       recieveKey   NoiseSymmetricKey
-       recieveNonce NoiseNonce
-       sendKey      NoiseSymmetricKey
-       sendNonce    NoiseNonce
-}
-
 type Peer struct {
        mutex                       sync.RWMutex
-       publicKey                   NoisePublicKey
-       presharedKey                NoiseSymmetricKey
-       endpoint                    net.IP
-       persistentKeepaliveInterval time.Duration
+       endpointIP                  net.IP        //
+       endpointPort                uint16        //
+       persistentKeepaliveInterval time.Duration // 0 = disabled
+       handshake                   Handshake
+       device                      *Device
+}
+
+func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
+       var peer Peer
+
+       // map public key
+
+       device.mutex.Lock()
+       device.peers[pk] = &peer
+       device.mutex.Unlock()
+
+       // precompute
+
+       peer.mutex.Lock()
+       peer.device = device
+       func(h *Handshake) {
+               h.mutex.Lock()
+               h.remoteStatic = pk
+               h.precomputedStaticStatic = device.privateKey.sharedSecret(h.remoteStatic)
+               h.mutex.Unlock()
+       }(&peer.handshake)
+       peer.mutex.Unlock()
+
+       return &peer
 }
index 0aa111ce604f4509a59945365830d301fbd9f0f5..553df117c3573a3cda8c83bb0cbdb159fd8493be 100644 (file)
@@ -13,6 +13,13 @@ type RoutingTable struct {
        mutex sync.RWMutex
 }
 
+func (table *RoutingTable) Reset() {
+       table.mutex.Lock()
+       defer table.mutex.Unlock()
+       table.IPv4 = nil
+       table.IPv6 = nil
+}
+
 func (table *RoutingTable) RemovePeer(peer *Peer) {
        table.mutex.Lock()
        defer table.mutex.Unlock()
index d0d1432eb52244018446ddd0a290059b85eae7e4..2299a3740d9746c30fedc3acf16c5604460b137d 100644 (file)
@@ -1,6 +1,7 @@
 package main
 
 import (
+       "bytes"
        "encoding/binary"
        "time"
 )
@@ -21,3 +22,7 @@ func Timestamp() TAI64N {
        binary.BigEndian.PutUint32(tai64n[8:], nano)
        return tai64n
 }
+
+func (t1 *TAI64N) After(t2 TAI64N) bool {
+       return bytes.Compare(t1[:], t2[:]) > 0
+}