diff --git a/core/core.go b/core/core.go
index 5157263d2..12205544f 100644
--- a/core/core.go
+++ b/core/core.go
@@ -156,6 +156,8 @@ func NewIpfsNode(cfg *config.Config, online bool) (n *IpfsNode, err error) {
 
 		// setup routing service
 		dhtRouting := dht.NewDHT(ctx, n.Identity, n.Peerstore, n.Network, dhtService, n.Datastore)
+		dhtRouting.Validators["ipns"] = namesys.ValidateIpnsRecord
+
 		// TODO(brian): perform this inside NewDHT factory method
 		dhtService.SetHandler(dhtRouting) // wire the handler to the service.
 		n.Routing = dhtRouting
diff --git a/namesys/internal/pb/namesys.pb.go b/namesys/internal/pb/namesys.pb.go
index b5d8885a2..81021b818 100644
--- a/namesys/internal/pb/namesys.pb.go
+++ b/namesys/internal/pb/namesys.pb.go
@@ -13,17 +13,50 @@ It has these top-level messages:
 */
 package namesys_pb
 
-import proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/proto"
+import proto "code.google.com/p/gogoprotobuf/proto"
 import math "math"
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
 var _ = math.Inf
 
+type IpnsEntry_ValidityType int32
+
+const (
+	// setting an EOL says "this record is valid until..."
+	IpnsEntry_EOL IpnsEntry_ValidityType = 0
+)
+
+var IpnsEntry_ValidityType_name = map[int32]string{
+	0: "EOL",
+}
+var IpnsEntry_ValidityType_value = map[string]int32{
+	"EOL": 0,
+}
+
+func (x IpnsEntry_ValidityType) Enum() *IpnsEntry_ValidityType {
+	p := new(IpnsEntry_ValidityType)
+	*p = x
+	return p
+}
+func (x IpnsEntry_ValidityType) String() string {
+	return proto.EnumName(IpnsEntry_ValidityType_name, int32(x))
+}
+func (x *IpnsEntry_ValidityType) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(IpnsEntry_ValidityType_value, data, "IpnsEntry_ValidityType")
+	if err != nil {
+		return err
+	}
+	*x = IpnsEntry_ValidityType(value)
+	return nil
+}
+
 type IpnsEntry struct {
-	Value            []byte `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
-	Signature        []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
-	XXX_unrecognized []byte `json:"-"`
+	Value            []byte                  `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
+	Signature        []byte                  `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
+	ValidityType     *IpnsEntry_ValidityType `protobuf:"varint,3,opt,name=validityType,enum=namesys.pb.IpnsEntry_ValidityType" json:"validityType,omitempty"`
+	Validity         []byte                  `protobuf:"bytes,4,opt,name=validity" json:"validity,omitempty"`
+	XXX_unrecognized []byte                  `json:"-"`
 }
 
 func (m *IpnsEntry) Reset()         { *m = IpnsEntry{} }
@@ -44,5 +77,20 @@ func (m *IpnsEntry) GetSignature() []byte {
 	return nil
 }
 
-func init() {
+func (m *IpnsEntry) GetValidityType() IpnsEntry_ValidityType {
+	if m != nil && m.ValidityType != nil {
+		return *m.ValidityType
+	}
+	return IpnsEntry_EOL
+}
+
+func (m *IpnsEntry) GetValidity() []byte {
+	if m != nil {
+		return m.Validity
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterEnum("namesys.pb.IpnsEntry_ValidityType", IpnsEntry_ValidityType_name, IpnsEntry_ValidityType_value)
 }
diff --git a/namesys/internal/pb/namesys.proto b/namesys/internal/pb/namesys.proto
index ac8a78da3..4219af6bb 100644
--- a/namesys/internal/pb/namesys.proto
+++ b/namesys/internal/pb/namesys.proto
@@ -1,6 +1,13 @@
 package namesys.pb;
 
 message IpnsEntry {
+	enum ValidityType {
+		// setting an EOL says "this record is valid until..."
+		EOL = 0;
+	}
 	required bytes value = 1;
 	required bytes signature = 2;
+
+	optional ValidityType validityType = 3;
+	optional bytes validity = 4;
 }
diff --git a/namesys/publisher.go b/namesys/publisher.go
index 9aaaa1cc3..7123264db 100644
--- a/namesys/publisher.go
+++ b/namesys/publisher.go
@@ -1,6 +1,8 @@
 package namesys
 
 import (
+	"bytes"
+	"errors"
 	"fmt"
 	"time"
 
@@ -14,6 +16,12 @@ import (
 	u "github.com/jbenet/go-ipfs/util"
 )
 
+// ErrExpiredRecord should be returned when an ipns record is
+// invalid due to being too old
+var ErrExpiredRecord = errors.New("expired record")
+
+var ErrUnrecognizedValidity = errors.New("unrecognized validity type")
+
 // ipnsPublisher is capable of publishing and resolving names to the IPFS
 // routing system.
 type ipnsPublisher struct {
@@ -76,11 +84,48 @@ func (p *ipnsPublisher) Publish(k ci.PrivKey, value string) error {
 
 func createRoutingEntryData(pk ci.PrivKey, val string) ([]byte, error) {
 	entry := new(pb.IpnsEntry)
-	sig, err := pk.Sign([]byte(val))
+
+	entry.Value = []byte(val)
+	typ := pb.IpnsEntry_EOL
+	entry.ValidityType = &typ
+	entry.Validity = []byte(time.Now().Add(time.Hour * 24).String())
+
+	sig, err := pk.Sign(ipnsEntryDataForSig(entry))
 	if err != nil {
 		return nil, err
 	}
 	entry.Signature = sig
-	entry.Value = []byte(val)
 	return proto.Marshal(entry)
 }
+
+func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
+	return bytes.Join([][]byte{
+		e.Value,
+		e.Validity,
+		[]byte(fmt.Sprint(e.GetValidityType())),
+	},
+		[]byte{})
+}
+
+func ValidateIpnsRecord(k u.Key, val []byte) error {
+	entry := new(pb.IpnsEntry)
+	err := proto.Unmarshal(val, entry)
+	if err != nil {
+		return err
+	}
+	switch entry.GetValidityType() {
+	case pb.IpnsEntry_EOL:
+		defaultTimeFormat := "2006-01-02 15:04:05.999999999 -0700 MST"
+		t, err := time.Parse(defaultTimeFormat, string(entry.GetValue()))
+		if err != nil {
+			log.Error("Failed parsing time for ipns record EOL")
+			return err
+		}
+		if time.Now().After(t) {
+			return ErrExpiredRecord
+		}
+	default:
+		return ErrUnrecognizedValidity
+	}
+	return nil
+}
diff --git a/namesys/routing.go b/namesys/routing.go
index c956f4c44..c990b492b 100644
--- a/namesys/routing.go
+++ b/namesys/routing.go
@@ -75,9 +75,11 @@ func (r *routingResolver) Resolve(name string) (string, error) {
 	if err != nil {
 		return "", err
 	}
+	hsh, _ := pk.Hash()
+	log.Debugf("pk hash = %s", u.Key(hsh))
 
 	// check sig with pk
-	if ok, err := pk.Verify(entry.GetValue(), entry.GetSignature()); err != nil || !ok {
+	if ok, err := pk.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok {
 		return "", fmt.Errorf("Invalid value. Not signed by PrivateKey corresponding to %v", pk)
 	}
 
diff --git a/routing/dht/dht.go b/routing/dht/dht.go
index 6b2d3f5bd..db17f9e7e 100644
--- a/routing/dht/dht.go
+++ b/routing/dht/dht.go
@@ -86,7 +86,6 @@ func NewDHT(ctx context.Context, p peer.Peer, ps peer.Peerstore, dialer inet.Dia
 	dht.birth = time.Now()
 
 	dht.Validators = make(map[string]ValidatorFunc)
-	dht.Validators["ipns"] = ValidateIpnsRecord
 	dht.Validators["pk"] = ValidatePublicKeyRecord
 
 	if doPinging {
diff --git a/routing/dht/records.go b/routing/dht/records.go
index c8397c960..ee1257e24 100644
--- a/routing/dht/records.go
+++ b/routing/dht/records.go
@@ -96,11 +96,6 @@ func (dht *IpfsDHT) verifyRecord(r *pb.Record) error {
 	return fnc(u.Key(r.GetKey()), r.GetValue())
 }
 
-func ValidateIpnsRecord(k u.Key, val []byte) error {
-	// TODO:
-	return nil
-}
-
 func ValidatePublicKeyRecord(k u.Key, val []byte) error {
 	keyparts := bytes.Split([]byte(k), []byte("/"))
 	if len(keyparts) < 3 {