Repository: scionproto/scion
Pull request #3808 (2020-06-30 13:00:46)

seg: support header v2 info and hop field
Add timestamp and segment ID to the `PathSegmentSignedData` to support
header v2 development.

Parse the raw legacy info field when parsing the path segment
and populate the timestamp.

This allows us not to return an error in the `Timestamp` method
and makes the API of seg.PathSegment a lot easier to use.

Remove `PathSegment.InfoF` as it taints everything with header v1
details. In most places it is only used for the timestamp anyway.

The control server now beacons all beacons with the segment info for
header v1 and header v2. The remaining part is to create the correct
hop fields.

Decouple the data plane representation from the control plane
representation of the hop field.

Introduce the `hopField` entry in the capnp segment representation
that will be used to store the new hop field representation.

For legacy hop fields, the new HopField struct is used to store the
parsed values. Thus, after parsing the path segment, even if it is
for a legacy path, the raw hop field no longer has to be accessed.
This comes with the great benefit, that `ID` and `FullID` computation
can no longer return an error. Further, because of the decoupling,
completely switching to the new header v2 will be a lot easier.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3808)
<!-- Reviewable:end -->


Oncilla: /rebase


Patch:
diff --git a/go/cs/beacon/beacondbsqlite/db.go b/go/cs/beacon/beacondbsqlite/db.go
index 424c1125a..5a55dcfc1 100644
--- a/go/cs/beacon/beacondbsqlite/db.go
+++ b/go/cs/beacon/beacondbsqlite/db.go
@@ -252,27 +252,17 @@ func (e *executor) InsertBeacon(ctx context.Context, b beacon.Beacon,
 
 	ret := beacon.InsertStats{}
 	// Compute ids outside of the lock.
-	segId, err := b.Segment.ID()
-	if err != nil {
-		return ret, db.NewInputDataError("extract id", err)
-	}
-	if _, err := b.Segment.FullId(); err != nil {
-		return ret, db.NewInputDataError("extract full id", err)
-	}
-	info, err := b.Segment.InfoF()
-	if err != nil {
-		return ret, db.NewInputDataError("extract infof", err)
-	}
+	segID := b.Segment.ID()
 
 	e.Lock()
 	defer e.Unlock()
-	meta, err := e.getBeaconMeta(ctx, segId)
+	meta, err := e.getBeaconMeta(ctx, segID)
 	if err != nil {
 		return ret, err
 	}
 	if meta != nil {
 		// Update the beacon data if it is newer.
-		if info.Timestamp().After(meta.InfoTime) {
+		if b.Segment.Timestamp().After(meta.InfoTime) {
 			meta.LastUpdated = time.Now()
 			if err := e.updateExistingBeacon(ctx, b, usage, meta.RowID, time.Now()); err != nil {
 				return ret, err
@@ -297,9 +287,9 @@ func (e *executor) InsertBeacon(ctx context.Context, b beacon.Beacon,
 
 // getBeaconMeta gets the metadata for existing beacons.
 func (e *executor) getBeaconMeta(ctx context.Context, segID common.RawBytes) (*beaconMeta, error) {
-	var rowId, infoTime, lastUpdated int64
+	var rowID, infoTime, lastUpdated int64
 	query := "SELECT RowID, InfoTime, LastUpdated FROM Beacons WHERE SegID=?"
-	err := e.db.QueryRowContext(ctx, query, segID).Scan(&rowId, &infoTime, &lastUpdated)
+	err := e.db.QueryRowContext(ctx, query, segID).Scan(&rowID, &infoTime, &lastUpdated)
 	// New beacons are not in the table.
 	if err == sql.ErrNoRows {
 		return nil, nil
@@ -308,7 +298,7 @@ func (e *executor) getBeaconMeta(ctx context.Context, segID common.RawBytes) (*b
 		return nil, db.NewReadError("Failed to lookup beacon", err)
 	}
 	meta := &beaconMeta{
-		RowID:       rowId,
+		RowID:       rowID,
 		InfoTime:    time.Unix(infoTime, 0),
 		LastUpdated: time.Unix(0, lastUpdated),
 	}
@@ -317,28 +307,21 @@ func (e *executor) getBeaconMeta(ctx context.Context, segID common.RawBytes) (*b
 
 // updateExistingBeacon updates the changeable data for an existing beacon
 func (e *executor) updateExistingBeacon(ctx context.Context, b beacon.Beacon,
-	usage beacon.Usage, rowId int64, now time.Time) error {
+	usage beacon.Usage, rowID int64, now time.Time) error {
 
-	fullId, err := b.Segment.FullId()
-	if err != nil {
-		return err
-	}
+	fullID := b.Segment.FullID()
 	packedSeg, err := b.Segment.Pack()
 	if err != nil {
 		return err
 	}
-	info, err := b.Segment.InfoF()
-	if err != nil {
-		return err
-	}
-	infoTime := info.Timestamp().Unix()
+	infoTime := b.Segment.Timestamp().Unix()
 	lastUpdated := now.UnixNano()
 	expTime := b.Segment.MaxExpiry().Unix()
 	inst := `UPDATE Beacons SET FullID=?, InIntfID=?, HopsLength=?, InfoTime=?,
 			ExpirationTime=?, LastUpdated=?, Usage=?, Beacon=?
 			WHERE RowID=?`
-	_, err = e.db.ExecContext(ctx, inst, fullId, b.InIfId, len(b.Segment.ASEntries), infoTime,
-		expTime, lastUpdated, usage, packedSeg, rowId)
+	_, err = e.db.ExecContext(ctx, inst, fullID, b.InIfId, len(b.Segment.ASEntries), infoTime,
+		expTime, lastUpdated, usage, packedSeg, rowID)
 	if err != nil {
 		return db.NewWriteError("update segment", err)
 	}
@@ -348,24 +331,14 @@ func (e *executor) updateExistingBeacon(ctx context.Context, b beacon.Beacon,
 func insertNewBeacon(ctx context.Context, tx *sql.Tx, b beacon.Beacon,
 	usage beacon.Usage, localIA addr.IA, now time.Time) error {
 
-	segId, err := b.Segment.ID()
-	if err != nil {
-		return db.NewInputDataError("extract id", err)
-	}
-	fullId, err := b.Segment.FullId()
-	if err != nil {
-		return db.NewInputDataError("extract full id", err)
-	}
+	segID := b.Segment.ID()
+	fullID := b.Segment.FullID()
 	packed, err := b.Segment.Pack()
 	if err != nil {
 		return db.NewInputDataError("pack segment", err)
 	}
-	info, err := b.Segment.InfoF()
-	if err != nil {
-		return db.NewInputDataError("extract infof", err)
-	}
 	start := b.Segment.FirstIA()
-	infoTime := info.Timestamp().Unix()
+	infoTime := b.Segment.Timestamp().Unix()
 	lastUpdated := now.UnixNano()
 	expTime := b.Segment.MaxExpiry().Unix()
 
@@ -375,24 +348,24 @@ func insertNewBeacon(ctx context.Context, tx *sql.Tx, b beacon.Beacon,
 		ExpirationTime, LastUpdated, Usage, Beacon)
 	VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 	`
-	res, err := tx.ExecContext(ctx, inst, segId, fullId, start.I, start.A, b.InIfId,
+	res, err := tx.ExecContext(ctx, inst, segID, fullID, start.I, start.A, b.InIfId,
 		len(b.Segment.ASEntries), infoTime, expTime, lastUpdated, usage, packed)
 	if err != nil {
 		return db.NewWriteError("insert beacon", err)
 	}
-	rowId, err := res.LastInsertId()
+	rowID, err := res.LastInsertId()
 	if err != nil {
 		return db.NewWriteError("retrieve RowID of inserted beacon", err)
 	}
 	// Insert all interfaces.
-	if err = insertInterfaces(ctx, tx, b, rowId, localIA); err != nil {
+	if err = insertInterfaces(ctx, tx, b, rowID, localIA); err != nil {
 		return err
 	}
 	return nil
 }
 
 func insertInterfaces(ctx context.Context, tx *sql.Tx, b beacon.Beacon,
-	rowId int64, localIA addr.IA) error {
+	rowID int64, localIA addr.IA) error {
 
 	stmtStr := `INSERT INTO IntfToBeacon (IsdID, AsID, IntfID, BeaconRowID)
 				VALUES (?, ?, ?, ?)`
@@ -404,13 +377,13 @@ func insertInterfaces(ctx context.Context, tx *sql.Tx, b beacon.Beacon,
 	for _, as := range b.Segment.ASEntries {
 		ia := as.IA()
 		// Do not insert peering interfaces.
-		hof, err := as.HopEntries[0].HopField()
+		hof := as.HopEntries[0].HopField
 		if err != nil {
 			return db.NewInputDataError("extract hop field", err)
 		}
 		// Ignore the null interface of the first hop.
 		if hof.ConsIngress != 0 {
-			_, err = stmt.ExecContext(ctx, ia.I, ia.A, hof.ConsIngress, rowId)
+			_, err = stmt.ExecContext(ctx, ia.I, ia.A, hof.ConsIngress, rowID)
 			if err != nil {
 				return db.NewWriteError("insert Ingress into IntfToSeg", err,
 					"ia", ia, "hof", hof)
@@ -418,14 +391,14 @@ func insertInterfaces(ctx context.Context, tx *sql.Tx, b beacon.Beacon,
 		}
 		// Ignore the null interface of the last hop
 		if hof.ConsEgress != 0 {
-			_, err := stmt.ExecContext(ctx, ia.I, ia.A, hof.ConsEgress, rowId)
+			_, err := stmt.ExecContext(ctx, ia.I, ia.A, hof.ConsEgress, rowID)
 			if err != nil {
 				return db.NewWriteError("insert Egress into IntfToSeg", err,
 					"ia", ia, "hof", hof)
 			}
 		}
 	}
-	_, err = stmt.ExecContext(ctx, localIA.I, localIA.A, b.InIfId, rowId)
+	_, err = stmt.ExecContext(ctx, localIA.I, localIA.A, b.InIfId, rowID)
 	if err != nil {
 		return db.NewWriteError("insert Ingress into IntfToSeg", err,
 			"ia", localIA, "inIfId", b.InIfId)
diff --git a/go/cs/beaconing/BUILD.bazel b/go/cs/beaconing/BUILD.bazel
index 2900b3236..67a363561 100644
--- a/go/cs/beaconing/BUILD.bazel
+++ b/go/cs/beaconing/BUILD.bazel
@@ -31,6 +31,7 @@ go_library(
         "//go/lib/log:go_default_library",
         "//go/lib/periodic:go_default_library",
         "//go/lib/serrors:go_default_library",
+        "//go/lib/slayers/path:go_default_library",
         "//go/lib/snet:go_default_library",
         "//go/lib/snet/addrutil:go_default_library",
         "//go/lib/spath:go_default_library",
diff --git a/go/cs/beaconing/extender.go b/go/cs/beaconing/extender.go
index 4a607ab31..3e1fb4e02 100644
--- a/go/cs/beaconing/extender.go
+++ b/go/cs/beaconing/extender.go
@@ -16,6 +16,7 @@ package beaconing
 
 import (
 	"context"
+	"encoding/binary"
 	"hash"
 	"time"
 
@@ -26,6 +27,7 @@ import (
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/serrors"
+	"github.com/scionproto/scion/go/lib/slayers/path"
 	"github.com/scionproto/scion/go/lib/spath"
 	"github.com/scionproto/scion/go/lib/topology"
 	"github.com/scionproto/scion/go/lib/util"
@@ -74,16 +76,12 @@ func (s *LegacyExtender) Extend(ctx context.Context, pseg *seg.PathSegment,
 	if ingress == 0 && egress == 0 {
 		return serrors.New("Ingress and egress must not be both 0")
 	}
-	infoF, err := pseg.InfoF()
-	if err != nil {
-		return common.NewBasicError("Unable to extract info field", err)
-	}
 	var prev common.RawBytes
 	if pseg.MaxAEIdx() >= 0 {
 		// Validated segments are guaranteed to have at least one hop entry.
 		prev = pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].RawHopField
 	}
-	hopEntries, err := s.createHopEntries(ingress, egress, peers, prev, infoF.Timestamp())
+	hopEntries, err := s.createHopEntries(ingress, egress, peers, prev, pseg.Timestamp())
 	if err != nil {
 		return err
 	}
@@ -148,6 +146,12 @@ func (s *LegacyExtender) createHopEntry(ingress, egress common.IFIDType, prev co
 		InMTU:       uint16(remoteInMtu),
 		RawOutIA:    remoteOutIA,
 		RemoteOutIF: remoteOutIfid,
+		HopField: seg.HopField{
+			ExpTime:     uint8(hopF.ExpTime),
+			ConsIngress: uint16(hopF.ConsIngress),
+			ConsEgress:  uint16(hopF.ConsEgress),
+			MAC:         hopF.Mac,
+		},
 	}
 	return hop, nil
 }
@@ -194,6 +198,163 @@ func (s *LegacyExtender) createHopF(ingress, egress common.IFIDType, prev common
 	return hop, nil
 }
 
+// DefaultExtender extends provided path segments with entries for the local AS.
+type DefaultExtender struct {
+	// IA is the local IA
+	IA addr.IA
+	// Signer is used to sign path segments.
+	Signer ctrl.Signer
+	// MAC is used to calculate the hop field MAC.
+	MAC func() hash.Hash
+	// Intfs holds all interfaces in the AS.
+	Intfs *ifstate.Interfaces
+	// MTU is the MTU value set in the AS entries.
+	MTU uint16
+	// GetMaxExpTime returns the maximum relative expiration time.
+	MaxExpTime func() uint8
+	// Task contains an identifier specific to the task that uses the extender.
+	Task string
+	// StaticInfo contains the configuration used for the StaticInfo Extension.
+	StaticInfo func() *StaticInfoCfg
+}
+
+// Extend extends the beacon with hop fields of the old format.
+func (s *DefaultExtender) Extend(ctx context.Context, pseg *seg.PathSegment,
+	ingress, egress common.IFIDType, peers []common.IFIDType) error {
+
+	if s.MTU == 0 {
+		return serrors.New("MTU not set")
+	}
+	if ingress == 0 && egress == 0 {
+		return serrors.New("Ingress and egress must not be both 0")
+	}
+	ts := pseg.Timestamp()
+	hopEntries, err := s.createHopEntries(ingress, egress, peers, ts, extractBeta(pseg))
+	if err != nil {
+		return err
+	}
+	asEntry := &seg.ASEntry{
+		RawIA:      s.IA.IAInt(),
+		IfIDSize:   16,
+		MTU:        s.MTU,
+		HopEntries: hopEntries,
+	}
+	if static := s.StaticInfo(); static != nil {
+		staticInfoPeers := createPeerMap(s.Intfs)
+		staticInfo := static.generateStaticinfo(staticInfoPeers, egress, ingress)
+		asEntry.Exts.StaticInfo = &staticInfo
+	}
+	if err := pseg.AddASEntry(ctx, asEntry, s.Signer); err != nil {
+		return err
+	}
+	if egress == 0 {
+		return pseg.Validate(seg.ValidateSegment)
+	}
+	return pseg.Validate(seg.ValidateBeacon)
+}
+
+func (s *DefaultExtender) createHopEntries(ingress, egress common.IFIDType, peers []common.IFIDType,
+	ts time.Time, beta uint16) ([]*seg.HopEntry, error) {
+
+	hopEntry, err := s.createHopEntry(ingress, egress, ts, beta)
+	if err != nil {
+		return nil, serrors.WrapStr("creating first entry", err)
+	}
+	thisBeta := beta ^ binary.BigEndian.Uint16(hopEntry.HopField.MAC[:2])
+	hopEntries := []*seg.HopEntry{hopEntry}
+	for _, peer := range peers {
+		peerEntry, err := s.createHopEntry(peer, egress, ts, thisBeta)
+		if err != nil {
+			log.Debug("Ignoring peer link upon error", "task", s.Task, "ifid", peer, "err", err)
+			continue
+		}
+		hopEntries = append(hopEntries, peerEntry)
+	}
+	return hopEntries, nil
+}
+
+func (s *DefaultExtender) createHopEntry(ingress, egress common.IFIDType, ts time.Time,
+	beta uint16) (*seg.HopEntry, error) {
+
+	remoteInIA, remoteInIfID, remoteInMTU, err := s.remoteInfo(ingress)
+	if err != nil {
+		return nil, serrors.WrapStr("checking remote ingress interface", err, "ifid", ingress)
+	}
+	remoteOutIA, remoteOutIfid, _, err := s.remoteInfo(egress)
+	if err != nil {
+		return nil, serrors.WrapStr("checking remote egress interface", err, "ifid", egress)
+	}
+	hopF := s.createHopF(uint16(ingress), uint16(egress), ts, beta)
+	hop := &seg.HopEntry{
+		RawInIA:     remoteInIA,
+		RemoteInIF:  remoteInIfID,
+		InMTU:       uint16(remoteInMTU),
+		RawOutIA:    remoteOutIA,
+		RemoteOutIF: remoteOutIfid,
+		HopField: seg.HopField{
+			ConsIngress: hopF.ConsIngress,
+			ConsEgress:  hopF.ConsEgress,
+			ExpTime:     hopF.ExpTime,
+			MAC:         hopF.Mac,
+		},
+	}
+	return hop, nil
+}
+
+func (s *DefaultExtender) remoteInfo(ifid common.IFIDType) (
+	addr.IAInt, common.IFIDType, uint16, error) {
+
+	if ifid == 0 {
+		return 0, 0, 0, nil
+	}
+	intf := s.Intfs.Get(ifid)
+	if intf == nil {
+		return 0, 0, 0, serrors.New("interface not found")
+	}
+	topoInfo := intf.TopoInfo()
+	if topoInfo.RemoteIFID == 0 {
+		return 0, 0, 0, serrors.New("remote ifid is not set")
+	}
+	if topoInfo.IA.IsWildcard() {
+		return 0, 0, 0, serrors.New("remote is wildcard", "isd_as", topoInfo.IA)
+	}
+	return topoInfo.IA.IAInt(), topoInfo.RemoteIFID, uint16(topoInfo.MTU), nil
+}
+
+func (s *DefaultExtender) createHopF(ingress, egress uint16, ts time.Time,
+	beta uint16) path.HopField {
+
+	expTime := s.MaxExpTime()
+	input := make([]byte, 16)
+	binary.BigEndian.PutUint32(input[:4], util.TimeToSecs(ts))
+	input[4] = expTime
+	binary.BigEndian.PutUint16(input[5:7], ingress)
+	binary.BigEndian.PutUint16(input[7:9], egress)
+	binary.BigEndian.PutUint16(input[9:11], beta)
+
+	mac := s.MAC()
+	// Write must not return an error: https://godoc.org/hash#Hash
+	if _, err := mac.Write(input); err != nil {
+		panic(err)
+	}
+	fullMAC := mac.Sum(nil)
+	return path.HopField{
+		ConsIngress: ingress,
+		ConsEgress:  egress,
+		ExpTime:     expTime,
+		Mac:         fullMAC[:6],
+	}
+}
+
+func extractBeta(pseg *seg.PathSegment) uint16 {
+	beta := pseg.SData.SegID
+	for _, entry := range pseg.ASEntries {
+		sigma := binary.BigEndian.Uint16(entry.HopEntries[0].HopField.MAC[:2])
+		beta = beta ^ sigma
+	}
+	return beta
+}
+
 func intfActive(intfs *ifstate.Interfaces, ifid common.IFIDType) bool {
 	intf := intfs.Get(ifid)
 	return intf != nil && intf.State() == ifstate.Active
diff --git a/go/cs/beaconing/originator.go b/go/cs/beaconing/originator.go
index aa8acc03a..416d87dea 100644
--- a/go/cs/beaconing/originator.go
+++ b/go/cs/beaconing/originator.go
@@ -16,6 +16,8 @@ package beaconing
 
 import (
 	"context"
+	"crypto/rand"
+	"math/big"
 	"net"
 	"time"
 
@@ -177,7 +179,20 @@ func (o *beaconOriginator) originateBeacon(ctx context.Context) error {
 }
 
 func (o *beaconOriginator) createBeacon(ctx context.Context) (*seg.Beacon, error) {
-	bseg, err := seg.NewSeg(&o.infoF)
+	rawInfo := make([]byte, spath.InfoFieldLength)
+	o.infoF.Write(rawInfo)
+
+	segID, err := rand.Int(rand.Reader, big.NewInt(1<<16))
+	if err != nil {
+		return nil, err
+	}
+	bseg, err := seg.NewSeg(
+		&seg.PathSegmentSignedData{
+			RawInfo:      rawInfo,
+			RawTimestamp: o.infoF.TsInt,
+			SegID:        uint16(segID.Uint64()),
+		},
+	)
 	if err != nil {
 		return nil, common.NewBasicError("Unable to create segment", err)
 	}
diff --git a/go/cs/segutil/filter.go b/go/cs/segutil/filter.go
index 6d110a003..38d619450 100644
--- a/go/cs/segutil/filter.go
+++ b/go/cs/segutil/filter.go
@@ -83,14 +83,13 @@ func wrap(seg *seg.PathSegment, dir Direction) segWrap {
 	keyParts := make([]string, 0, len(seg.ASEntries))
 	for _, asEntry := range seg.ASEntries {
 		for _, hopEntry := range asEntry.HopEntries {
-			hopField, err := hopEntry.HopField()
-			// This library expects the segments to be verified first.
-			if err != nil {
-				panic(err)
-			}
-			for _, ifid := range []common.IFIDType{hopField.ConsIngress, hopField.ConsEgress} {
+			hopField := hopEntry.HopField
+			for _, ifid := range []uint16{hopField.ConsIngress, hopField.ConsEgress} {
 				if ifid != 0 {
-					intfs = append(intfs, pathInterface{ia: asEntry.IA(), ifid: ifid})
+					intfs = append(intfs, pathInterface{
+						ia:   asEntry.IA(),
+						ifid: common.IFIDType(ifid),
+					})
 					keyParts = append(keyParts, fmt.Sprintf("%s#%d", asEntry.IA(), ifid))
 				}
 			}
diff --git a/go/lib/ctrl/seg/as.go b/go/lib/ctrl/seg/as.go
index dbc40d345..f58586f11 100644
--- a/go/lib/ctrl/seg/as.go
+++ b/go/lib/ctrl/seg/as.go
@@ -22,6 +22,7 @@ import (
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/scrypto"
+	"github.com/scionproto/scion/go/lib/spath"
 	"github.com/scionproto/scion/go/proto"
 )
 
@@ -44,7 +45,23 @@ type ASEntry struct {
 
 func NewASEntryFromRaw(b common.RawBytes) (*ASEntry, error) {
 	ase := &ASEntry{}
-	return ase, proto.ParseFromRaw(ase, b)
+	if err := proto.ParseFromRaw(ase, b); err != nil {
+		return nil, err
+	}
+	// FIXME(roosd): Remove when we switch to new header format completely.
+	for _, entry := range ase.HopEntries {
+		if len(entry.RawHopField) != 0 {
+			hopF, err := spath.HopFFromRaw(entry.RawHopField)
+			if err != nil {
+				return nil, err
+			}
+			entry.HopField.ExpTime = uint8(hopF.ExpTime)
+			entry.HopField.ConsIngress = uint16(hopF.ConsIngress)
+			entry.HopField.ConsEgress = uint16(hopF.ConsEgress)
+			entry.HopField.MAC = hopF.Mac
+		}
+	}
+	return ase, nil
 }
 
 func (ase *ASEntry) IA() addr.IA {
diff --git a/go/lib/ctrl/seg/hop.go b/go/lib/ctrl/seg/hop.go
index c0957a26a..95dabead3 100644
--- a/go/lib/ctrl/seg/hop.go
+++ b/go/lib/ctrl/seg/hop.go
@@ -19,7 +19,6 @@ package seg
 import (
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
-	"github.com/scionproto/scion/go/lib/spath"
 )
 
 type HopEntry struct {
@@ -28,6 +27,9 @@ type HopEntry struct {
 	InMTU       uint16     `capnp:"inMTU"`
 	RawOutIA    addr.IAInt `capnp:"outIA"`
 	RemoteOutIF common.IFIDType
+	HopField    HopField `capnp:"hopField"`
+
+	// Deprecated: This is only used for legacy path header
 	RawHopField []byte `capnp:"hopF"`
 }
 
@@ -39,6 +41,9 @@ func (e *HopEntry) OutIA() addr.IA {
 	return e.RawOutIA.IA()
 }
 
-func (e *HopEntry) HopField() (*spath.HopField, error) {
-	return spath.HopFFromRaw(e.RawHopField)
+type HopField struct {
+	ExpTime     uint8  `capnp:"expTime"`
+	ConsIngress uint16 `capnp:"consIngress"`
+	ConsEgress  uint16 `capnp:"consEgress"`
+	MAC         []byte `capnp:"mac"`
 }
diff --git a/go/lib/ctrl/seg/seg.go b/go/lib/ctrl/seg/seg.go
index dfd6e96b0..5994b85b9 100644
--- a/go/lib/ctrl/seg/seg.go
+++ b/go/lib/ctrl/seg/seg.go
@@ -22,7 +22,6 @@ import (
 	"crypto/sha256"
 	"encoding/binary"
 	"fmt"
-	"io"
 	"strings"
 	"time"
 
@@ -93,20 +92,17 @@ const (
 var _ proto.Cerealizable = (*PathSegment)(nil)
 
 type PathSegment struct {
-	RawSData     common.RawBytes        `capnp:"sdata"`
+	RawSData     []byte                 `capnp:"sdata"`
 	SData        *PathSegmentSignedData `capnp:"-"`
 	RawASEntries []*proto.SignedBlobS   `capnp:"asEntries"`
 	// ASEntries contains the AS entries.
 	// WARNING: Should never be modified! Use AddASEntry or create a new Segment instead.
 	ASEntries []*ASEntry `capnp:"-"`
-	id        common.RawBytes
-	fullId    common.RawBytes
 }
 
 // NewSeg creates a new path segment with the specified info field. The AS
 // entries are empty and should be added using AddASEntry.
-func NewSeg(infoF *spath.InfoField) (*PathSegment, error) {
-	pss := newPathSegmentSignedData(infoF)
+func NewSeg(pss *PathSegmentSignedData) (*PathSegment, error) {
 	rawPss, err := proto.PackRoot(pss)
 	if err != nil {
 		return nil, err
@@ -116,17 +112,17 @@ func NewSeg(infoF *spath.InfoField) (*PathSegment, error) {
 }
 
 // NewSegFromRaw creates a segment from raw data.
-func NewSegFromRaw(b common.RawBytes) (*PathSegment, error) {
+func NewSegFromRaw(b []byte) (*PathSegment, error) {
 	return newSegFromRaw(b, ValidateSegment)
 }
 
 // NewBeaconFromRaw creates a segment from raw data. The last AS entry is
 // not assumed to terminate the path segment.
-func NewBeaconFromRaw(b common.RawBytes) (*PathSegment, error) {
+func NewBeaconFromRaw(b []byte) (*PathSegment, error) {
 	return newSegFromRaw(b, ValidateBeacon)
 }
 
-func newSegFromRaw(b common.RawBytes, validationMethod ValidationMethod) (*PathSegment, error) {
+func newSegFromRaw(b []byte, validationMethod ValidationMethod) (*PathSegment, error) {
 	ps := &PathSegment{}
 	err := proto.ParseFromRaw(ps, b)
 	if err != nil {
@@ -141,6 +137,7 @@ func (ps *PathSegment) ParseRaw(validationMethod ValidationMethod) error {
 	if err != nil {
 		return err
 	}
+
 	ps.ASEntries = make([]*ASEntry, len(ps.RawASEntries))
 	for i, rawASEntry := range ps.RawASEntries {
 		ps.ASEntries[i], err = NewASEntryFromRaw(rawASEntry.Blob)
@@ -152,50 +149,32 @@ func (ps *PathSegment) ParseRaw(validationMethod ValidationMethod) error {
 }
 
 // ID returns a hash of the segment covering all hops, except for peerings.
-func (ps *PathSegment) ID() (common.RawBytes, error) {
-	if ps.id == nil {
-		id, err := ps.calculateHash(true)
-		if err != nil {
-			return nil, err
-		}
-		ps.id = id
-	}
-	return ps.id, nil
+func (ps *PathSegment) ID() []byte {
+	return ps.calculateHash(true)
 }
 
-// FullId returns a hash of the segment covering all hops including peerings.
-func (ps *PathSegment) FullId() (common.RawBytes, error) {
-	if ps.fullId == nil {
-		fullId, err := ps.calculateHash(false)
-		if err != nil {
-			return nil, err
-		}
-		ps.fullId = fullId
-	}
-	return ps.fullId, nil
+// FullID returns a hash of the segment covering all hops including peerings.
+func (ps *PathSegment) FullID() []byte {
+	return ps.calculateHash(false)
 }
 
-func (ps *PathSegment) calculateHash(hopOnly bool) (common.RawBytes, error) {
+func (ps *PathSegment) calculateHash(hopOnly bool) []byte {
 	h := sha256.New()
 	for _, ase := range ps.ASEntries {
 		binary.Write(h, binary.BigEndian, ase.RawIA)
 		for _, hopE := range ase.HopEntries {
-			hopf, err := hopE.HopField()
-			if err != nil {
-				return nil, err
-			}
-			binary.Write(h, binary.BigEndian, hopf.ConsIngress)
-			binary.Write(h, binary.BigEndian, hopf.ConsEgress)
+			binary.Write(h, binary.BigEndian, hopE.HopField.ConsIngress)
+			binary.Write(h, binary.BigEndian, hopE.HopField.ConsEgress)
 			if hopOnly {
 				break
 			}
 		}
 	}
-	return h.Sum(nil), nil
+	return h.Sum(nil)
 }
 
-func (ps *PathSegment) InfoF() (*spath.InfoField, error) {
-	return ps.SData.InfoF()
+func (ps *PathSegment) Timestamp() time.Time {
+	return util.SecsToTime(ps.SData.RawTimestamp)
 }
 
 // Validate validates that remote ingress and egress ISD-AS for each AS
@@ -230,22 +209,13 @@ func (ps *PathSegment) Validate(validationMethod ValidationMethod) error {
 			return common.NewBasicError("Unable to validate AS entry", err, "ASEntryIdx", i)
 		}
 	}
-	// Check that all hop fields can be extracted
-	if err := ps.WalkHopEntries(); err != nil {
-		return err
-	}
 	return nil
 }
 
 func (ps *PathSegment) ContainsInterface(ia addr.IA, ifid common.IFIDType) bool {
 	for _, asEntry := range ps.ASEntries {
 		for _, entry := range asEntry.HopEntries {
-			hf, err := entry.HopField()
-			if err != nil {
-				// This should not happen, as Validate already checks that it
-				// is possible to extract the hop field.
-				panic(err)
-			}
+			hf, ifid := entry.HopField, uint16(ifid)
 			if asEntry.IA().Equal(ia) && (hf.ConsEgress == ifid || hf.ConsIngress == ifid) {
 				return true
 			}
@@ -273,27 +243,16 @@ func (ps *PathSegment) MinExpiry() time.Time {
 func (ps *PathSegment) expiry(initTtl time.Duration,
 	compare func(time.Duration, time.Duration) bool) time.Time {
 
-	info, err := ps.InfoF()
-	if err != nil {
-		// This should not happen, as Validate already checks that infoF can be parsed.
-		panic(err)
-	}
 	ttl := initTtl
 	for _, asEntry := range ps.ASEntries {
 		for _, he := range asEntry.HopEntries {
-			hf, err := he.HopField()
-			if err != nil {
-				// This should not happen, as Validate already checks that it
-				// is possible to extract the hop field.
-				panic(err)
-			}
-			hfTtl := hf.ExpTime.ToDuration()
-			if compare(hfTtl, ttl) {
-				ttl = hfTtl
+			hfTTL := spath.ExpTimeType(he.HopField.ExpTime).ToDuration()
+			if compare(hfTTL, ttl) {
+				ttl = hfTTL
 			}
 		}
 	}
-	return info.Timestamp().Add(ttl)
+	return ps.Timestamp().Add(ttl)
 }
 
 // FirstIA returns the IA of the first ASEntry.
@@ -311,13 +270,17 @@ func (ps *PathSegment) LastIA() addr.IA {
 // WalkHopEntries iterates through the hop entries of asEntries, checking that
 // the hop fields within can be parsed. If an parse error is found, the
 // function immediately returns with an error.
+//
+// Deprecated: This will no longer be needed with the header redesign.
 func (ps *PathSegment) WalkHopEntries() error {
 	for _, asEntry := range ps.ASEntries {
 		for _, hopEntry := range asEntry.HopEntries {
-			_, err := hopEntry.HopField()
-			if err != nil {
-				return common.NewBasicError("invalid hop field found in ASEntry",
-					err, "asEntry", asEntry)
+			if len(hopEntry.RawHopField) != 0 {
+				_, err := spath.HopFFromRaw(hopEntry.RawHopField)
+				if err != nil {
+					return common.NewBasicError("invalid hop field found in ASEntry",
+						err, "asEntry", asEntry)
+				}
 			}
 		}
 	}
@@ -338,7 +301,6 @@ func (ps *PathSegment) AddASEntry(ctx context.Context, ase *ASEntry, signer Sign
 		ps.popLastEntry()
 		return err
 	}
-	ps.invalidateIds()
 	return nil
 }
 
@@ -347,11 +309,6 @@ func (ps *PathSegment) popLastEntry() {
 	ps.ASEntries = ps.ASEntries[:len(ps.ASEntries)-1]
 }
 
-func (ps *PathSegment) invalidateIds() {
-	ps.id = nil
-	ps.fullId = nil
-}
-
 // VerifyASEntry verifies the AS Entry at the specified index.
 func (ps *PathSegment) VerifyASEntry(ctx context.Context, verifier Verifier, idx int) error {
 	if err := ps.validateIdx(idx); err != nil {
@@ -395,8 +352,6 @@ func (ps *PathSegment) ShallowCopy() *PathSegment {
 		SData:        ps.SData,
 		RawASEntries: rawEntries,
 		ASEntries:    entries,
-		id:           ps.id,
-		fullId:       ps.fullId,
 	}
 }
 
@@ -404,36 +359,6 @@ func (ps *PathSegment) Write(b common.RawBytes) (int, error) {
 	return proto.WriteRoot(ps, b)
 }
 
-// RawWriteTo writes the PathSegment to the writer in a form that is understood by spath/Path.
-func (ps *PathSegment) RawWriteTo(w io.Writer) (int64, error) {
-	var total int64
-	inf, err := ps.InfoF()
-	if err != nil {
-		return total, err
-	}
-	inf.Hops = uint8(len(ps.ASEntries))
-	n, err := inf.WriteTo(w)
-	total += n
-	if err != nil {
-		return total, err
-	}
-	for _, asEntry := range ps.ASEntries {
-		if len(asEntry.HopEntries) == 0 {
-			return total, common.NewBasicError("ASEntry has no HopEntry", nil, "asEntry", asEntry)
-		}
-		hf, err := asEntry.HopEntries[0].HopField()
-		if err != nil {
-			return total, err
-		}
-		n, err = hf.WriteTo(w)
-		total += n
-		if err != nil {
-			return total, err
-		}
-	}
-	return total, nil
-}
-
 func (ps *PathSegment) Pack() (common.RawBytes, error) {
 	return proto.PackRoot(ps)
 }
@@ -446,21 +371,16 @@ func (ps *PathSegment) String() string {
 	if ps == nil {
 		return "<nil>"
 	}
-	info, _ := ps.InfoF()
 	desc := []string{
 		ps.GetLoggingID(),
-		util.TimeToCompact(info.Timestamp()),
+		util.TimeToCompact(ps.Timestamp()),
 		ps.getHopsDescription(),
 	}
 	return strings.Join(desc, " ")
 }
 
 func (ps *PathSegment) GetLoggingID() string {
-	id, err := ps.ID()
-	if err != nil {
-		return fmt.Sprintf("ID error: %s", err)
-	}
-	return id.String()[:12]
+	return fmt.Sprintf("%x", ps.ID()[:12])
 }
 
 func (ps *PathSegment) getHopsDescription() string {
@@ -474,10 +394,7 @@ func (ps *PathSegment) getHopsDescription() string {
 }
 
 func getHopDescription(ia addr.IA, hopEntry *HopEntry) string {
-	hop, err := hopEntry.HopField()
-	if err != nil {
-		return err.Error()
-	}
+	hop := hopEntry.HopField
 	hop_desc := []string{}
 	if hop.ConsIngress > 0 {
 		hop_desc = append(hop_desc, fmt.Sprintf("%v ", hop.ConsIngress))
diff --git a/go/lib/ctrl/seg/signed.go b/go/lib/ctrl/seg/signed.go
index c233d7ff6..f86cb4732 100644
--- a/go/lib/ctrl/seg/signed.go
+++ b/go/lib/ctrl/seg/signed.go
@@ -26,27 +26,35 @@ import (
 var _ proto.Cerealizable = (*PathSegmentSignedData)(nil)
 
 type PathSegmentSignedData struct {
-	RawInfo common.RawBytes `capnp:"infoF"`
-}
+	RawTimestamp uint32 `capnp:"timestamp"`
+	SegID        uint16 `capnp:"segID"`
 
-func newPathSegmentSignedData(infoF *spath.InfoField) *PathSegmentSignedData {
-	pss := &PathSegmentSignedData{RawInfo: make(common.RawBytes, spath.InfoFieldLength)}
-	infoF.Write(pss.RawInfo)
-	return pss
+	// TODO(roosd): Remove when completely switching to v2.
+	RawInfo common.RawBytes `capnp:"infoF"`
 }
 
-func NewPathSegmentSignedDataFromRaw(b common.RawBytes) (*PathSegmentSignedData, error) {
+func NewPathSegmentSignedDataFromRaw(b []byte) (*PathSegmentSignedData, error) {
 	pss := &PathSegmentSignedData{}
-	return pss, proto.ParseFromRaw(pss, b)
-}
-
-func (pss *PathSegmentSignedData) InfoF() (*spath.InfoField, error) {
-	return spath.InfoFFromRaw(pss.RawInfo)
+	if err := proto.ParseFromRaw(pss, b); err != nil {
+		return nil, err
+	}
+	if len(pss.RawInfo) == 0 {
+		return pss, nil
+	}
+	info, err := spath.InfoFFromRaw(pss.RawInfo)
+	if err != nil {
+		return nil, err
+	}
+	pss.RawTimestamp = info.TsInt
+	return pss, nil
 }
 
 func (pss *PathSegmentSignedData) Validate() error {
-	_, err := pss.InfoF()
-	return err
+	if len(pss.RawInfo) > 0 {
+		_, err := spath.InfoFFromRaw(pss.RawInfo)
+		return err
+	}
+	return nil
 }
 
 func (pss *PathSegmentSignedData) ProtoId() proto.ProtoIdType {
@@ -54,7 +62,7 @@ func (pss *PathSegmentSignedData) ProtoId() proto.ProtoIdType {
 }
 
 func (pss *PathSegmentSignedData) String() string {
-	info, err := pss.InfoF()
+	info, err := spath.InfoFFromRaw(pss.RawInfo)
 	if err != nil {
 		return fmt.Sprintf("InfoF: %s (parse err: %s)", pss.RawInfo, err)
 	}
diff --git a/go/lib/infra/modules/combinator/combinator.go b/go/lib/infra/modules/combinator/combinator.go
index fd889d61b..fb994081e 100644
--- a/go/lib/infra/modules/combinator/combinator.go
+++ b/go/lib/infra/modules/combinator/combinator.go
@@ -30,6 +30,7 @@ import (
 	"time"
 
 	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/sciond"
 	"github.com/scionproto/scion/go/lib/spath"
@@ -143,7 +144,7 @@ type Segment struct {
 // initInfoFieldFrom copies the info field in pathSegment, and sets it as the
 // info field of segment.
 func (segment *Segment) initInfoFieldFrom(pathSegment *seg.PathSegment) {
-	infoField, err := pathSegment.InfoF()
+	infoField, err := spath.InfoFFromRaw(pathSegment.SData.RawInfo)
 	if err != nil {
 		panic(err)
 	}
@@ -154,12 +155,13 @@ func (segment *Segment) initInfoFieldFrom(pathSegment *seg.PathSegment) {
 
 // appendHopFieldFrom copies the Hop Field in entry, and appends it to segment.
 func (segment *Segment) appendHopFieldFrom(entry *seg.HopEntry) *HopField {
-	inputHopField, err := entry.HopField()
-	if err != nil {
-		panic(err)
-	}
 	hopField := &HopField{
-		HopField: inputHopField,
+		HopField: &spath.HopField{
+			ExpTime:     spath.ExpTimeType(entry.HopField.ExpTime),
+			ConsIngress: common.IFIDType(entry.HopField.ConsIngress),
+			ConsEgress:  common.IFIDType(entry.HopField.ConsEgress),
+			Mac:         append([]byte(nil), entry.HopField.MAC...),
+		},
 	}
 	segment.HopFields = append(segment.HopFields, hopField)
 	if segment.InfoField.Hops == 0xff {
diff --git a/go/lib/infra/modules/combinator/graph.go b/go/lib/infra/modules/combinator/graph.go
index ee654fa68..512fa90ed 100644
--- a/go/lib/infra/modules/combinator/graph.go
+++ b/go/lib/infra/modules/combinator/graph.go
@@ -116,10 +116,6 @@ func (g *DMG) traverseSegment(segment *InputSegment) {
 		// Construct edges for each hop in the current ASEntry.
 		for hopEntryIndex, hop := range asEntries[asEntryIndex].HopEntries {
 			weight := len(asEntries) - 1 - asEntryIndex
-			he, err := hop.HopField()
-			if err != nil {
-				panic(err)
-			}
 
 			// build new edge
 			var srcVertex, dstVertex Vertex
@@ -132,7 +128,8 @@ func (g *DMG) traverseSegment(segment *InputSegment) {
 				}
 				dstVertex = VertexFromIA(currentIA)
 			} else {
-				dstVertex = VertexFromPeering(currentIA, he.ConsIngress, hop.InIA(), hop.RemoteInIF)
+				ingress := common.IFIDType(hop.HopField.ConsIngress)
+				dstVertex = VertexFromPeering(currentIA, ingress, hop.InIA(), hop.RemoteInIF)
 			}
 
 			if segment.Type == proto.PathSegType_down {
@@ -389,14 +386,8 @@ func (sl PathSolutionList) Less(i, j int) bool {
 	}
 
 	for ki := range trailI {
-		idI, err := trailI[ki].segment.ID()
-		if err != nil {
-			panic(err)
-		}
-		idJ, err := trailJ[ki].segment.ID()
-		if err != nil {
-			panic(err)
-		}
+		idI := trailI[ki].segment.ID()
+		idJ := trailJ[ki].segment.ID()
 		idcmp := bytes.Compare(idI, idJ)
 		if idcmp != 0 {
 			return idcmp == -1
diff --git a/go/lib/infra/modules/combinator/staticinfo_accumulator.go b/go/lib/infra/modules/combinator/staticinfo_accumulator.go
index f6ccbc621..acf4f515e 100644
--- a/go/lib/infra/modules/combinator/staticinfo_accumulator.go
+++ b/go/lib/infra/modules/combinator/staticinfo_accumulator.go
@@ -213,8 +213,7 @@ func extractUpOverdata(res *PathMetadata, oldASEntry *seg.ASEntry, newASEntry *s
 	ia := newASEntry.IA()
 	staticInfo := oldASEntry.Exts.StaticInfo
 	hopEntry := newASEntry.HopEntries[0]
-	hf, _ := hopEntry.HopField()
-	newIngressIfID := hf.ConsIngress
+	newIngressIfID := common.IFIDType(hopEntry.HopField.ConsIngress)
 	for i := 0; i < len(staticInfo.Latency.Childlatencies); i++ {
 		if staticInfo.Latency.Childlatencies[i].IfID == newIngressIfID {
 			res.ASLatencies[ia] = ASLatency{
@@ -259,8 +258,7 @@ func extractCoreOverdata(res *PathMetadata, oldASEntry *seg.ASEntry, newASEntry
 	staticInfo := newASEntry.Exts.StaticInfo
 	oldSI := oldASEntry.Exts.StaticInfo
 	hopEntry := oldASEntry.HopEntries[0]
-	hf, _ := hopEntry.HopField()
-	oldEgressIfID := hf.ConsEgress
+	oldEgressIfID := common.IFIDType(hopEntry.HopField.ConsEgress)
 	for i := 0; i < len(staticInfo.Latency.Childlatencies); i++ {
 		if staticInfo.Latency.Childlatencies[i].IfID == oldEgressIfID {
 			res.ASLatencies[ia] = ASLatency{
@@ -410,6 +408,5 @@ func getGeo(asEntry *seg.ASEntry) ASGeo {
 }
 
 func peerIfID(he *seg.HopEntry) common.IFIDType {
-	PE, _ := he.HopField()
-	return PE.ConsIngress
+	return common.IFIDType(he.HopField.ConsIngress)
 }
diff --git a/go/lib/pathdb/sqlite/sqlite.go b/go/lib/pathdb/sqlite/sqlite.go
index 859e66c78..faf24d71e 100644
--- a/go/lib/pathdb/sqlite/sqlite.go
+++ b/go/lib/pathdb/sqlite/sqlite.go
@@ -140,26 +140,15 @@ func (e *executor) InsertWithHPCfgIDs(ctx context.Context, segMeta *seg.Meta,
 	}
 	pseg := segMeta.Segment
 	// Check if we already have a path segment.
-	segID, err := pseg.ID()
-	if err != nil {
-		return noInsertion, err
-	}
-	newFullId, err := pseg.FullId()
-	if err != nil {
-		return noInsertion, err
-	}
-	newInfo, err := pseg.InfoF()
-	if err != nil {
-		return noInsertion, err
-	}
+	segID := pseg.ID()
+	newFullId := pseg.FullID()
 	meta, err := e.get(ctx, segID)
 	if err != nil {
 		return noInsertion, err
 	}
 	if meta != nil {
 		// Check if the new segment is more recent.
-		curInfo, _ := meta.Seg.InfoF()
-		if newInfo.Timestamp().After(curInfo.Timestamp()) {
+		if pseg.Timestamp().After(meta.Seg.Timestamp()) {
 			// Update existing path segment.
 			meta.Seg = pseg
 			meta.LastUpdated = time.Now()
@@ -241,19 +230,12 @@ func updateSeg(ctx context.Context, tx *sql.Tx, meta *segMeta) error {
 	if err != nil {
 		return err
 	}
-	info, err := meta.Seg.InfoF()
-	if err != nil {
-		return err
-	}
 	exp := meta.Seg.MaxExpiry().Unix()
-	fullID, err := meta.Seg.FullId()
-	if err != nil {
-		return err
-	}
+	fullID := meta.Seg.FullID()
 	stmtStr := `UPDATE Segments SET FullID=?, LastUpdated=?, InfoTs=?, Segment=?, MaxExpiry=?
 				WHERE RowID=?`
 	_, err = tx.ExecContext(ctx, stmtStr,
-		fullID, meta.LastUpdated.UnixNano(), info.Timestamp(), packedSeg, exp, meta.RowID)
+		fullID, meta.LastUpdated.UnixNano(), meta.Seg.Timestamp(), packedSeg, exp, meta.RowID)
 	if err != nil {
 		return common.NewBasicError("Failed to update segment", err)
 	}
@@ -287,22 +269,12 @@ func insertFull(ctx context.Context, tx *sql.Tx, segMeta *seg.Meta,
 	hpCfgIDs []*query.HPCfgID) error {
 
 	pseg := segMeta.Segment
-	segID, err := pseg.ID()
-	if err != nil {
-		return err
-	}
-	fullID, err := pseg.FullId()
-	if err != nil {
-		return err
-	}
+	segID := pseg.ID()
+	fullID := pseg.FullID()
 	packedSeg, err := pseg.Pack()
 	if err != nil {
 		return err
 	}
-	info, err := pseg.InfoF()
-	if err != nil {
-		return err
-	}
 	st := pseg.FirstIA()
 	end := pseg.LastIA()
 	exp := pseg.MaxExpiry().Unix()
@@ -311,7 +283,7 @@ func insertFull(ctx context.Context, tx *sql.Tx, segMeta *seg.Meta,
 			StartIsdID, StartAsID, EndIsdID, EndAsID)
 			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
 	res, err := tx.ExecContext(ctx, inst, segID, fullID, time.Now().UnixNano(),
-		info.Timestamp().UnixNano(), packedSeg, exp, st.I, st.A, end.I, end.A)
+		pseg.Timestamp().UnixNano(), packedSeg, exp, st.I, st.A, end.I, end.A)
 	if err != nil {
 		return common.NewBasicError("Failed to insert path segment", err)
 	}
@@ -348,10 +320,7 @@ func insertInterfaces(ctx context.Context, tx *sql.Tx,
 	for _, as := range ases {
 		ia := as.IA()
 		for idx, hop := range as.HopEntries {
-			hof, err := hop.HopField()
-			if err != nil {
-				return common.NewBasicError("Failed to extract hop field", err)
-			}
+			hof := hop.HopField
 			if hof.ConsIngress != 0 {
 				_, err = stmt.ExecContext(ctx, ia.I, ia.A, hof.ConsIngress, segRowID)
 				if err != nil {
diff --git a/go/lib/revcache/util.go b/go/lib/revcache/util.go
index b72b7f060..accb30408 100644
--- a/go/lib/revcache/util.go
+++ b/go/lib/revcache/util.go
@@ -17,6 +17,7 @@ package revcache
 import (
 	"context"
 
+	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/infra/modules/cleaner"
@@ -87,17 +88,12 @@ func addRevKeys(segs []*seg.PathSegment, keys KeySet, hopOnly bool) {
 	for _, s := range segs {
 		for _, asEntry := range s.ASEntries {
 			for _, entry := range asEntry.HopEntries {
-				hf, err := entry.HopField()
-				if err != nil {
-					// This should not happen, as Validate already checks that it
-					// is possible to extract the hop field.
-					panic(err)
-				}
+				hf := entry.HopField
 				if hf.ConsIngress != 0 {
-					keys[*NewKey(asEntry.IA(), hf.ConsIngress)] = struct{}{}
+					keys[*NewKey(asEntry.IA(), common.IFIDType(hf.ConsIngress))] = struct{}{}
 				}
 				if hf.ConsEgress != 0 {
-					keys[*NewKey(asEntry.IA(), hf.ConsEgress)] = struct{}{}
+					keys[*NewKey(asEntry.IA(), common.IFIDType(hf.ConsEgress))] = struct{}{}
 				}
 				if hopOnly {
 					break
diff --git a/go/lib/snet/addrutil/BUILD.bazel b/go/lib/snet/addrutil/BUILD.bazel
index 1b45fc1a6..a0e60f87f 100644
--- a/go/lib/snet/addrutil/BUILD.bazel
+++ b/go/lib/snet/addrutil/BUILD.bazel
@@ -9,6 +9,7 @@ go_library(
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/seg:go_default_library",
+        "//go/lib/serrors:go_default_library",
         "//go/lib/snet:go_default_library",
         "//go/lib/spath:go_default_library",
         "//go/lib/topology:go_default_library",
diff --git a/go/lib/snet/addrutil/addrutil.go b/go/lib/snet/addrutil/addrutil.go
index 5ad3c7873..ff01cb287 100644
--- a/go/lib/snet/addrutil/addrutil.go
+++ b/go/lib/snet/addrutil/addrutil.go
@@ -21,6 +21,7 @@ import (
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
+	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/snet"
 	"github.com/scionproto/scion/go/lib/spath"
 	"github.com/scionproto/scion/go/lib/topology"
@@ -28,26 +29,25 @@ import (
 
 // GetPath creates a path from the given segment and then creates a snet.SVCAddr.
 func GetPath(svc addr.HostSVC, ps *seg.PathSegment, topoProv topology.Provider) (net.Addr, error) {
-	x := &bytes.Buffer{}
-	if _, err := ps.RawWriteTo(x); err != nil {
-		return nil, common.NewBasicError("failed to write segment to buffer", err)
+	p, err := legacyPath(ps)
+	if err != nil {
+		return nil, serrors.WrapStr("constructing path from segment", err)
 	}
-	p := spath.New(x.Bytes())
 	if err := p.Reverse(); err != nil {
-		return nil, common.NewBasicError("failed to reverse path", err)
+		return nil, serrors.WrapStr("reversing path", err)
 	}
 	if err := p.InitOffsets(); err != nil {
-		return nil, common.NewBasicError("failed to init offsets", err)
+		return nil, serrors.WrapStr("initializing offsets", err)
 	}
 	hopF, err := p.GetHopField(p.HopOff)
 	if err != nil {
-		return nil, common.NewBasicError("failed to extract first HopField", err, "p", p)
+		return nil, serrors.WrapStr("extracting first hop field", err)
 	}
 	topo := topoProv.Get()
 	ifID := hopF.ConsIngress
 	UnderlayNextHop, ok := topo.UnderlayNextHop2(ifID)
 	if !ok {
-		return nil, common.NewBasicError("unable to find first-hop BR for path", nil, "ifID", ifID)
+		return nil, serrors.New("first-hop border router not found", "intf_id", ifID)
 	}
 	return &snet.SVCAddr{IA: ps.FirstIA(), Path: p, NextHop: UnderlayNextHop, SVC: svc}, nil
 }
@@ -63,3 +63,37 @@ func ResolveLocal(dst net.IP) (net.IP, error) {
 	srcIP := udpConn.LocalAddr().(*net.UDPAddr).IP
 	return srcIP, nil
 }
+
+// legacyPath constructs a spath.Path from the path segment in construction direction.
+func legacyPath(ps *seg.PathSegment) (*spath.Path, error) {
+	info, err := spath.InfoFFromRaw(ps.SData.RawInfo)
+	if err != nil {
+		return nil, err
+	}
+	inf := spath.InfoField{
+		ConsDir: true,
+		Hops:    uint8(len(ps.ASEntries)),
+		TsInt:   info.TsInt,
+		ISD:     info.ISD,
+	}
+
+	buf := bytes.Buffer{}
+	if _, err = inf.WriteTo(&buf); err != nil {
+		return nil, err
+	}
+	for _, asEntry := range ps.ASEntries {
+		if len(asEntry.HopEntries) == 0 {
+			return nil, serrors.New("ASEntry has no HopEntry", "asEntry", asEntry)
+		}
+		hf := spath.HopField{
+			ExpTime:     spath.ExpTimeType(asEntry.HopEntries[0].HopField.ExpTime),
+			ConsIngress: common.IFIDType(asEntry.HopEntries[0].HopField.ConsIngress),
+			ConsEgress:  common.IFIDType(asEntry.HopEntries[0].HopField.ConsEgress),
+			Mac:         asEntry.HopEntries[0].HopField.MAC,
+		}
+		if _, err = hf.WriteTo(&buf); err != nil {
+			return nil, err
+		}
+	}
+	return &spath.Path{Raw: buf.Bytes()}, nil
+}
diff --git a/go/lib/spath/hop.go b/go/lib/spath/hop.go
index 194866859..d6b225949 100644
--- a/go/lib/spath/hop.go
+++ b/go/lib/spath/hop.go
@@ -96,6 +96,7 @@ func HopFFromRaw(b []byte) (*HopField, error) {
 }
 
 func (h *HopField) Write(b common.RawBytes) {
+	_ = b[7]
 	var flags uint8
 	if h.Xover {
 		flags |= XoverMask
diff --git a/go/proto/path_seg.capnp.go b/go/proto/path_seg.capnp.go
index 552e6b7e2..4aaaa2e32 100644
--- a/go/proto/path_seg.capnp.go
+++ b/go/proto/path_seg.capnp.go
@@ -104,12 +104,12 @@ type PathSegmentSignedData struct{ capnp.Struct }
 const PathSegmentSignedData_TypeID = 0xc7cf7a18177aec2a
 
 func NewPathSegmentSignedData(s *capnp.Segment) (PathSegmentSignedData, error) {
-	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
 	return PathSegmentSignedData{st}, err
 }
 
 func NewRootPathSegmentSignedData(s *capnp.Segment) (PathSegmentSignedData, error) {
-	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
 	return PathSegmentSignedData{st}, err
 }
 
@@ -137,12 +137,28 @@ func (s PathSegmentSignedData) SetInfoF(v []byte) error {
 	return s.Struct.SetData(0, v)
 }
 
+func (s PathSegmentSignedData) Timestamp() uint32 {
+	return s.Struct.Uint32(0)
+}
+
+func (s PathSegmentSignedData) SetTimestamp(v uint32) {
+	s.Struct.SetUint32(0, v)
+}
+
+func (s PathSegmentSignedData) SegID() uint16 {
+	return s.Struct.Uint16(4)
+}
+
+func (s PathSegmentSignedData) SetSegID(v uint16) {
+	s.Struct.SetUint16(4, v)
+}
+
 // PathSegmentSignedData_List is a list of PathSegmentSignedData.
 type PathSegmentSignedData_List struct{ capnp.List }
 
 // NewPathSegmentSignedData creates a new list of PathSegmentSignedData.
 func NewPathSegmentSignedData_List(s *capnp.Segment, sz int32) (PathSegmentSignedData_List, error) {
-	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}, sz)
 	return PathSegmentSignedData_List{l}, err
 }
 
@@ -413,17 +429,18 @@ func (p ASEntry_exts_Promise) StaticInfo() StaticInfoExtn_Promise {
 }
 
 type HopEntry struct{ capnp.Struct }
+type HopEntry_hopField HopEntry
 
 // HopEntry_TypeID is the unique identifier for the type HopEntry.
 const HopEntry_TypeID = 0x8bb1ddafb4872b0b
 
 func NewHopEntry(s *capnp.Segment) (HopEntry, error) {
-	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 40, PointerCount: 1})
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 40, PointerCount: 2})
 	return HopEntry{st}, err
 }
 
 func NewRootHopEntry(s *capnp.Segment) (HopEntry, error) {
-	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 40, PointerCount: 1})
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 40, PointerCount: 2})
 	return HopEntry{st}, err
 }
 
@@ -491,12 +508,52 @@ func (s HopEntry) SetHopF(v []byte) error {
 	return s.Struct.SetData(0, v)
 }
 
+func (s HopEntry) HopField() HopEntry_hopField { return HopEntry_hopField(s) }
+
+func (s HopEntry_hopField) ExpTime() uint8 {
+	return s.Struct.Uint8(18)
+}
+
+func (s HopEntry_hopField) SetExpTime(v uint8) {
+	s.Struct.SetUint8(18, v)
+}
+
+func (s HopEntry_hopField) ConsIngress() uint16 {
+	return s.Struct.Uint16(20)
+}
+
+func (s HopEntry_hopField) SetConsIngress(v uint16) {
+	s.Struct.SetUint16(20, v)
+}
+
+func (s HopEntry_hopField) ConsEgress() uint16 {
+	return s.Struct.Uint16(22)
+}
+
+func (s HopEntry_hopField) SetConsEgress(v uint16) {
+	s.Struct.SetUint16(22, v)
+}
+
+func (s HopEntry_hopField) Mac() ([]byte, error) {
+	p, err := s.Struct.Ptr(1)
+	return []byte(p.Data()), err
+}
+
+func (s HopEntry_hopField) HasMac() bool {
+	p, err := s.Struct.Ptr(1)
+	return p.IsValid() || err != nil
+}
+
+func (s HopEntry_hopField) SetMac(v []byte) error {
+	return s.Struct.SetData(1, v)
+}
+
 // HopEntry_List is a list of HopEntry.
 type HopEntry_List struct{ capnp.List }
 
 // NewHopEntry creates a new list of HopEntry.
 func NewHopEntry_List(s *capnp.Segment, sz int32) (HopEntry_List, error) {
-	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 40, PointerCount: 1}, sz)
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 40, PointerCount: 2}, sz)
 	return HopEntry_List{l}, err
 }
 
@@ -517,6 +574,18 @@ func (p HopEntry_Promise) Struct() (HopEntry, error) {
 	return HopEntry{s}, err
 }
 
+func (p HopEntry_Promise) HopField() HopEntry_hopField_Promise {
+	return HopEntry_hopField_Promise{p.Pipeline}
+}
+
+// HopEntry_hopField_Promise is a wrapper for a HopEntry_hopField promised by a client call.
+type HopEntry_hopField_Promise struct{ *capnp.Pipeline }
+
+func (p HopEntry_hopField_Promise) Struct() (HopEntry_hopField, error) {
+	s, err := p.Pipeline.Struct()
+	return HopEntry_hopField{s}, err
+}
+
 type PCB struct{ capnp.Struct }
 
 // PCB_TypeID is the unique identifier for the type PCB.
@@ -758,68 +827,77 @@ func (l PathSegType_List) Set(i int, v PathSegType) {
 	ul.Set(i, uint16(v))
 }
 
-const schema_fb8053d9fb34b837 = "x\xda\xacT]h\x1cU\x18\xfd\xce\xfdfw\x93H" +
-	"\x9c\x1dv\xa5\xa9\x08kEAW[jZM)J" +
-	"L\xed_\x84\xe0\xde\xa6\xb6\xe8\x83v\xba;I\x06\x92" +
-	"\xd9a\xe7.\xcdF\xa4y\x89?h\xf1\xc1\"\xa64" +
-	"\xa4-\x0a\x06\x1a\xac\xd8B\x12,$\xa5P\x04\xc1\x17" +
-	"\x11\x8d\x14\xac\xb6\xd4\x82o>\x88-:rw3\xbb" +
-	"K\xd2\xbe\xf9v\xe7\x9bs\xbf\xef\x9es\xcf\xb9\x9b[" +
-	"\xc5\x0b\xe2\xe9\xd8\x15A$\x1f\x8e\xc5\xc3\xfb\x9e|\xe7" +
-	"\xfc\x17W\xbf|\x9f\xa4\x89X\xd85\xb7\xf5\xceO\xfd" +
-	"\xe3w(\x86\x04Q\xaa\x17\x97RR\xaf\xb6\xf4\xe1 " +
-	"\x08\xe1m\x7f\xe1\xed\x13\x8b'?\"\xcbD\x13Xh" +
-	"\xf0i\xb1\x9c\x9a\xad\xaef\xc4\x11Bh.\xb4=\xf7" +
-	"\xe6\x81O\xa6ug\xac\xee\xbc\x9e\x97S\x8f\xb1^m" +
-	"`\x0d\x1e\xdfv\xa6\xcd\x99\xf9\xeb4Y\xa6h`\x09" +
-	"\xa9\xb7x9\xf5^\x158\xc1\x83\x840\x9c\x9e\x98\xfa" +
-	"\xb7M]\"\xb9\x0eF\x18\xaawo\xfe\xdez\xe6{" +
-	"z \x96\x80\x9e\xcc\xbf\x12R\xb3|\x93\x10f\xff\x18" +
-	"[\xd71\xf6\xdd\x95Ug\xad\xd29fd\x91\x9a2" +
-	"t\xdbI\xa3[\xb7\x8d\x1aI\x13F\x13:\xa6!\xf3" +
-	"\xc6BjI\x83\xb7\\4>\xd42\xdc\x90\x95\xae\xfc" +
-	"\x9e\xc5\x1f\xeeJ\xed\xf9\xf8\x89\xd4\xae\xb8^\xf5\xc45" +
-	"5\xdfVCo\x04\xce\xa0\xd8\x94\xb7}\xcf\xdf\xbe\xb7" +
-	"\xe8\xef\xf2T\xa9B9@v\xb0Ad\x80\xc8\x9a\xcc" +
-	"\x12\xc9\xe3\x0cyJ\xc0\x02\xd2\xd0\xc5\xa9\xd7\x88\xe4I" +
-	"\x86\xfc\\\xc0\x12-i\x08\"\xeb\xb3N\"y\x8a!" +
-	"\xcf\x0aX\xcci0\x915\xa3\x8b\x9f2\xe49\x01\xcb" +
-	"0\xd20\x88\xac\xd9\xc3D\xf2,C\xce\x09 \x96F" +
-	"\x8c\xc8\xba\xa0\xe7\x9cc\xc8\xaf\x05L\xd7\xeb\xedA+" +
-	"\x09\xb4\x12\xc2\x923RTN\xafG\xdc\xbb;*f" +
-	"\\\xafo\xff+H\x90@\x82\x90)\x96\xd5\x9a\x0d/" +
-	"\x97)\xa1\x1a;\xcc\xa1\xa2\xbf\x1b\xed$\xd0~\x17\xf2" +
-	"9[\x0d\xf5;\x83#\x0e{J\xf3o\xa9\xf3\x7fB" +
-	"\x13x\x94!77\xf1\xdf\xb8\x8fH>\xc5\x90{\x05" +
-	"2A\xc1Vv\xbd\xb3\x1dh\x15]\x87\x10\xe0~B" +
-	"\x8e\x81d\xb8\xef\xb7\xdb]\x13{:\xa7\x89\xa0\x8b\xf7" +
-	"\x1a\xdf\xe7\xb0\xb2W\x8d\xdf\xd1\x18_\x9f\xae\xa5z\x9c" +
-	"!\xb7\x0a\x1c\xf5k[\x91l\x84\x80\x80$\xc1T\x15" +
-	"\xdf\x81\xd9\xb00\x01\xe6\xbdg\xef\xaf\xb0\xef\xe8\xd9\xc9" +
-	"\xeaej\xda\x80\xb5\xe1A\"\x08k}\x96\x08lY" +
-	"Y\xa2L\xd9\x0b\x1c\xc5e\xdf,\x14\x8fxf\xbeX" +
-	"r\xd6\xb4\xec\xe9\xaf:i\x93\x93\x18U\x81L\xb2\x91" +
-	"D\x1aq\"\xcb.\x11\xc9C\x0c9\xac\xc5\x14im" +
-	"L\xcb\xd5\x0a\x17\x18\xd2\xd7f\xe24Z\x88\xac\x11\x8d" +
-	"\x1cf\xc8Qm&#\x8dV\"\xab\xacm\xa7\x18r" +
-	"\\ ,\x15\xcb\xca\xf5\x06s\x94)\x0e\xbb\xf9\x0a\x92" +
-	"\xe1\xdf\xd7\x9f\xd96\xb7\xbc\xf4\xf1\x8a\x00\x99\xc0=\\" +
-	"\xb2\x91\x0c\x8f\x1d\xba\xf5\xea\xfc\xad\x7f\xe6V\xea\xe1\x90" +
-	"[(8^\xce\xa6L$\xdd\x9f\x1f\xdc\xb86\xf9U" +
-	"%\x8c\x10\x81\xb2\x95\x9b\xd7\x96\x1b(\"\x19\x1e\xdc\xe8" +
-	"\x1c\x7f\xf6\xf5\xdc\xcf\xd1\xef\x88/\xafv\x8f\xa7\xfa\xdd" +
-	"A\xcf)\x98;\xed\xdaE\x1a\xf5\x8bl\xd7,[\x18" +
-	"2-\xb4{\x07\x8ak\xcd\x88H\xbe\xee\x9a~\xba\xc1" +
-	"C\xf5\x06\x17:\x1b\x01\xa9\x1bq~;\x91<\xcf\x90" +
-	"\x8bZ;Q\x0b\xe2E\xed\x999\x86\xbc\xac\xb5\xeb\xa8" +
-	"\x05q\xe9%\"\xb9\xc8\x90\xdf\x0a`%\x87\xdfh#" +
-	"]f\xc8_\x04\xacX{-\x88W\x1f!\x92?2" +
-	"\xe4u\x01\xc4\xd1\xf4\xb4Y\xd7\xb2$2nP\xb0\x83" +
-	"(U\xdd\xaa\x94?\xe0\x94\xa2\xcf\xa3y\xa7\xa4\x9a\xbe" +
-	"Cw\xa0wg\xbf;\xe6\x10\x11\xe2$\x10\xaf\x05\xb1" +
-	")\x1a\xf5\x87\xbe\x16\x8d\xc4\x88*G\xb16\x9dQ\x15" +
-	"\xacQ'\xc7/\xee\xf8\xdf2\xa2\xcf\x17\x1d\xf6\xbf\x00" +
-	"\x00\x00\xff\xffe%\x9c\x88"
+const schema_fb8053d9fb34b837 = "x\xda\xacUo\x88\x14e\x1c\xfe=\xef\xbb\xb3sw" +
+	"f\xb3\xd3\xacxE\xb0\x14~\xa8+\xc5\xd4R$\xf0" +
+	"O\xfe[Q\xda\xd7\xbb\xac$\xcaq\xf7uo\xe0v" +
+	"v\xdcy\x17o\xad\xf0\xbeXAE\x1f\x8a()\x8e" +
+	"\xfc\xd6'\x8b\x0c\xce\xc3\xe0N\x94\x0c\x94\x904\xf4B" +
+	"\xc8TL(\"\"B\xc5&\xde\xdd\x9d\xdd\xe5\xee\xfc" +
+	"\xd6\xb7\x9d\xdf>\xef\xfc~\xcf\xf3>\xbfg\x16\x8f\xb0" +
+	"\xd5\xec\x09#\xc3\x89\xc4\x02#\x19\xcdy\xec\xcd#\x87" +
+	"/}\xf96\x09\x0bF\xb4|l\xd9\xed\x8b\xfd#\xb7" +
+	"\xc9`&\x91S\xc5q\xe7u\x98DKkx\x0f\x84" +
+	"\xe8V0\xfe\xc6\xc1\x89O\xde'\xdb\xc2t\xf0\x9fl" +
+	"\xca\xb9S\xffu\x93\xed%D\xd6x\xcf\xd3\xafn\xff" +
+	"hT\xbf\xb9\x13\xac\xdf\xe7x|\xca\xa9r\xfdk\x0f" +
+	"\xd7\xe0\x91\x15\x87z\xe4\xe7\xff|F\xb6\xc5\xdaX\x82" +
+	"s\x86O9\x17\xeb\xc0\xf3\xbcH\x88\xa2\xd1\x03\x9f\xfe" +
+	"\xdb\xa3\x8e\x93\x98\x8fD\x14\xa9\xb7\xae\xff\xda}\xe8\x1c" +
+	"\xcd3L\xe8\xce\xfc\x17\x82s\x87_'D\xe3\xb2\xf7" +
+	"\xca\xe8\x0f\x7f\x9c$\xd1\x0b\xa3\xcds\x1e\xd3\xc8\xa5\x97" +
+	"\x12= 8\xd7\x12\x1a\xda\xf7\xdb\xbe\xf9\xbd\xfb\xbe\xff" +
+	"v\xb6Q\x97N\x1a}p\xce\x1az\x843\xc6a=" +
+	"B\xdcTXHt\xa0\xeb\x10/9\xee\xecI\xeas" +
+	"\xa5d]\xb2k\xa2\xb6<\xbfq\xe2\xc7Ye\xf8\xdd" +
+	"<\xe8\xfcm\xd6\xd53\xb5\x0c\x81\xab\x06_\x09e\x91" +
+	"-\xca\xbb\x81\x1f\xac\xdcT\x0e\xd6\xfb\xaaR\xa3\x1c " +
+	"\x1e\xe4\x09\xa2\x04\x88\xec\xaf\xfb\x88\xc4\x17\x1c\xe2\x18\x83" +
+	"\x0d\xa4\xa1\x8bGw\x10\x891\x0eq\x82\xc1f]i" +
+	"0\"{r\x09\x918\xc6!N1\xd8\x9c\xa7\xc1\x89" +
+	"\xec\x93\xba8\xc1!N3\xd8\x89D\x1a\x09\"\xfb\xbb" +
+	"]D\xe2\x14\x878\xc7\x00#\x0d\x83\xc8>\xab\xfb\x9c" +
+	"\xe6\x10\x17\x18\x90D\x87\xa6\xf6\xf9\xcd\xc4,\xcf\xcf\xae" +
+	"A71t\x13\xa2\x8a,\x95\x95\xcc\xfa\xc4\xb3\x1b\xe2" +
+	"b\xc6\xf3\xb7\x0e<\x07\x93\x18LB\xa6\\U3\x0e" +
+	"<[%S\xb5OX\x83\xe5`\x03\xe6\x12\xc3\\B" +
+	"\xa4\x1f<9T \xa2\x19\xda\xe4\\5\xd8/\x8b%" +
+	"\xc9}\xa5\xe5\xe9j\xc9\xf3\xa8\xe6\xb7\x80C,\xee\x90" +
+	"g\xe16\"\xf18\x87\xd8\xc4\x90\x09\x0b\xaer[]" +
+	"\xdcP\x8b\xecIB\x88{\x099\x0e\xa4\xa2mWn" +
+	"-?\xb0q\xc9(\x11t\xf1n\xed\xb7J\xae\xdci" +
+	"\xed\xd7\xb6\xdb\xb7\xbak%\x1f\xe1\x10\xcb\x18\xf6\x07\x8d" +
+	"\xa3H\xb5\xf7\x89\x80\x14\xc1R\xb5@\xc2jo\x03\x01" +
+	"\xd6\xdd{\x0f\xd4x u\xefT\xfd\xae5m\xc0~" +
+	"\xe8\x01\"0\xfb\xfe>\"p\xdb\xee#\xcaT\xfdP" +
+	"*^\x0d\xacBy\xafo\xe5\xcb\x159\xe3\x95k\xfa" +
+	"\xebF[$\xcda\x15\x8a\x14O\xa4\x90F\x92\xc8v" +
+	"+Db'\x87\x18\xd2b\xb2\xb4\xf6\xad\xedi\x85\x0b" +
+	"\x1c\"\xd0^\xe3it\x11\xd9%\x8d\x1c\xe2\x10\xc3\xda" +
+	"k\x894\xba\x89\xec\xaav\xa5\xe2\x10#\x0cQ\xa5\\" +
+	"U\x9e_\xccQ\xa6<\xe4\xe5kHE7\xaf>\xb9" +
+	"blj\xf2\xc3\xa6\x00\x99\xd0\xdbUq\x91\x8a\xde\xdd" +
+	"y\xe3\xc5\xa37\xee\x8c5\xeb\xd1\xa0W(H?\xe7" +
+	"R&\x96\xee\xafw\xae]\xfe\xf8\xabZ\x14#B\xe5" +
+	"*/\xaf\xed\xb7\xbb\x8cT\xf4\xfcB\xf9\xc1S/\xe7" +
+	"~\x8a\xff\x8e\xf9\xf2i\x9b\xb5\xa8m\xb1:\xeb\xfb\x9a" +
+	"\xac\xf5\x1d\xbe\xc4!\x065\xeb\x9e\x06k\xb9\xab\x93\xf5" +
+	"\x9c&\xeb\x1d\x9d\xac\xd1d\xfd0\x91\x088\xc4k\x0c" +
+	"\xfb\xe5p0\xe0\x95$\x92\xc4\x90$D\xf9\xb2\x1ff" +
+	"\xfdb\x85L\x19\x86\xf1n\xd4\xab\xeb\x8b\x15I\xbc]" +
+	"4Kn\xbe\xe5\xd2\xe9\x0cZ\xfe\xf7U\xbfW\xf4e" +
+	"\xc1Z\xe76\xacxO\xcb\x8a\xeb\xf5=\xad\xe6\x10[" +
+	"\xdaV\xcc\xeaE\xd8\xc4!\x064\x0b\xd6\xc8\x09\xa1\x81" +
+	"[8\xc4\x0bL/\xed\xeer{\x07\x95W\x92\xa1r" +
+	"K\x84\x00]\xc4\xd0\xa5\xafI\x16\xb3\xebZ\xa3\xc7\x93" +
+	"!\xf6\xd2\xaa\x86\x99\xa6\x85\xd6\x92\xd9Bk%\x918" +
+	"\xc2!&:\x86\xf9fmG\x92\xf1\xdeFhMn" +
+	"n\x87\x16\xe2\xcc\xd2[u\x82C\xfc\xcc`\x1bs\x1b" +
+	"\xa1uIk\x7f\x81C\\m\x86V\xeb\x93a_\xee" +
+	"#\x96\xf1\xc2\x82\x1b\xc6q\xb3JU\xf2\xdbe%~" +
+	"\xdc\x9f\x97\x15\xd5\xf1\x1cy\xbb\xb3\xeb\xfa\xbd}\x92\x88" +
+	"\xe2\xfb\xd3\x09\xd5\x91\x13\xad\x0fK#'\xcc\x92\xaa\xc6" +
+	"\xc2XrX\x853\xd4\xc9\xf1g\xd6\xfeo\x81\xa1\xe7" +
+	"\x8b\x87\xfd/\x00\x00\xff\xff=\x88\xf2\xaf"
 
 func init() {
 	schemas.Register(schema_fb8053d9fb34b837,
@@ -828,6 +906,7 @@ func init() {
 		0x9f98567b3c0aba0f,
 		0xa1f7a9650aa23880,
 		0xc2740afe9d859fff,
+		0xc5efd39fe31865ba,
 		0xc7cf7a18177aec2a,
 		0xd4a209e8e78874ff,
 		0xd6c04763377951e5)
diff --git a/go/tools/pathdb_dump/BUILD.bazel b/go/tools/pathdb_dump/BUILD.bazel
index 715a572fc..1c6313cc9 100644
--- a/go/tools/pathdb_dump/BUILD.bazel
+++ b/go/tools/pathdb_dump/BUILD.bazel
@@ -8,7 +8,6 @@ go_library(
     visibility = ["//visibility:private"],
     deps = [
         "//go/lib/addr:go_default_library",
-        "//go/lib/common:go_default_library",
         "//go/lib/env:go_default_library",
         "//go/lib/pathdb/query:go_default_library",
         "//go/lib/pathdb/sqlite:go_default_library",
diff --git a/go/tools/pathdb_dump/pathdb_dump.go b/go/tools/pathdb_dump/pathdb_dump.go
index 0d7d959d4..6cccbf4b5 100644
--- a/go/tools/pathdb_dump/pathdb_dump.go
+++ b/go/tools/pathdb_dump/pathdb_dump.go
@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"github.com/scionproto/scion/go/lib/addr"
-	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/env"
 	"github.com/scionproto/scion/go/lib/pathdb/query"
 	"github.com/scionproto/scion/go/lib/pathdb/sqlite"
@@ -89,7 +88,7 @@ func realMain() error {
 
 type asIface struct {
 	IA    addr.IA
-	ifNum common.IFIDType
+	ifNum uint16
 }
 
 type segment struct {
@@ -103,10 +102,7 @@ type segment struct {
 func newSegment(res *query.Result) (segment, error) {
 	ifs := make([]asIface, 0, len(res.Seg.ASEntries))
 	for _, ase := range res.Seg.ASEntries {
-		hop, err := ase.HopEntries[0].HopField()
-		if err != nil {
-			return segment{}, err
-		}
+		hop := ase.HopEntries[0].HopField
 		if hop.ConsIngress > 0 {
 			iface := asIface{
 				IA:    ase.IA(),
diff --git a/proto/path_seg.capnp b/proto/path_seg.capnp
index bda8c5b73..d63dd2775 100644
--- a/proto/path_seg.capnp
+++ b/proto/path_seg.capnp
@@ -16,6 +16,8 @@ struct PathSegment {
 # Contains all top-level signed data for PathSegment
 struct PathSegmentSignedData {
     infoF @0 :Data; # Raw InfoField
+    timestamp @1 :UInt32; # Timestamp in seconds since epoch.
+    segID @2 :UInt16;
 }
 
 struct ASEntry {
@@ -34,12 +36,18 @@ struct ASEntry {
 }
 
 struct HopEntry {
-    inIA @0 :UInt64;  # Ingress (incl peer) ISD-AS
-    remoteInIF @1 :UInt64; # Interface ID on far end of ingress link
-    inMTU @2 :UInt16;  # Ingress Link MTU
-    outIA @3 :UInt64;  # Downstream ISD-AS
+    inIA        @0 :UInt64;  # Ingress (incl peer) ISD-AS
+    remoteInIF  @1 :UInt64;  # Interface ID on far end of ingress link
+    inMTU       @2 :UInt16;  # Ingress Link MTU
+    outIA       @3 :UInt64;  # Downstream ISD-AS
     remoteOutIF @4 :UInt64;  # Interface ID on far end of egress link
-    hopF @5 :Data;  # Raw HopField
+    hopF        @5 :Data;    # Raw HopField (deprecated)
+    hopField :group {
+        expTime     @6 :UInt8;   # Relative expiry time.
+        consIngress @7 :UInt16;  # Interface ID of the ingress link.
+        consEgress  @8 :UInt16;   # Interface ID of the egress link.
+        mac         @9 :Data;
+    }
 }
 
 # PathSegment Construction Beacon - used during path beaconing.


Test patch:
diff --git a/go/cs/beacon/beacondbtest/beacondbtest.go b/go/cs/beacon/beacondbtest/beacondbtest.go
index 0c13fc837..bb5fa02c5 100644
--- a/go/cs/beacon/beacondbtest/beacondbtest.go
+++ b/go/cs/beacon/beacondbtest/beacondbtest.go
@@ -664,10 +664,6 @@ func CheckResults(t *testing.T, results <-chan beacon.BeaconOrErr,
 			assert.NoError(t, res.Err, "Beacon %d err", i)
 			require.NotNil(t, res.Beacon.Segment, "Beacon %d segment", i)
 			// Make sure the segment is properly initialized.
-			_, err := res.Beacon.Segment.ID()
-			require.NoError(t, err)
-			_, err = res.Beacon.Segment.FullId()
-			require.NoError(t, err)
 			assert.Equal(t, expected.Segment, res.Beacon.Segment, "Segment %d should match", i)
 			assert.Equal(t, expected.InIfId, res.Beacon.InIfId, "InIfId %d should match", i)
 		case <-time.After(timeout):
@@ -756,10 +752,10 @@ func AllocBeacon(t *testing.T, ctrl *gomock.Controller, ases []IfInfo, inIfId co
 			next = ases[i+1].IA
 		}
 		hops := []*seg.HopEntry{
-			allocHopEntry(prev, next, allocRawHop(as.Ingress, as.Egress)),
+			allocHopEntry(prev, next, as.Ingress, as.Egress),
 		}
 		for _, peer := range as.Peers {
-			hops = append(hops, allocHopEntry(peer.IA, next, allocRawHop(peer.Ingress, as.Egress)))
+			hops = append(hops, allocHopEntry(peer.IA, next, peer.Ingress, as.Egress))
 		}
 
 		entries[i] = &seg.ASEntry{
@@ -767,12 +763,20 @@ func AllocBeacon(t *testing.T, ctrl *gomock.Controller, ases []IfInfo, inIfId co
 			HopEntries: hops,
 		}
 	}
-	info := &spath.InfoField{
-		TsInt: infoTS,
+
+	rawInfo := make([]byte, spath.InfoFieldLength)
+	(&spath.InfoField{
 		ISD:   1,
-		Hops:  uint8(len(ases)),
-	}
-	pseg, err := seg.NewSeg(info)
+		TsInt: infoTS,
+	}).Write(rawInfo)
+
+	pseg, err := seg.NewSeg(
+		&seg.PathSegmentSignedData{
+			RawInfo:      rawInfo,
+			RawTimestamp: infoTS,
+			SegID:        10, // XXX(roosd): deterministic beacon needed.
+		},
+	)
 	require.NoError(t, err)
 	signer := mock_seg.NewMockSigner(ctrl)
 	signer.EXPECT().Sign(gomock.Any(), gomock.AssignableToTypeOf(common.RawBytes{})).Return(
@@ -781,28 +785,27 @@ func AllocBeacon(t *testing.T, ctrl *gomock.Controller, ases []IfInfo, inIfId co
 		err := pseg.AddASEntry(context.Background(), entry, signer)
 		require.NoError(t, err)
 	}
-	segID, err := pseg.ID()
-	require.NoError(t, err)
-	_, err = pseg.FullId()
-	require.NoError(t, err)
-	return beacon.Beacon{Segment: pseg, InIfId: inIfId}, segID
+	return beacon.Beacon{Segment: pseg, InIfId: inIfId}, pseg.ID()
 }
 
-func allocRawHop(ingress, egress common.IFIDType) common.RawBytes {
+func allocHopEntry(inIA, outIA addr.IA, ingress, egress common.IFIDType) *seg.HopEntry {
 	raw := make([]byte, 8)
-	hf := spath.HopField{
+	hopF := spath.HopField{
 		ConsIngress: ingress,
 		ConsEgress:  egress,
 		ExpTime:     spath.DefaultHopFExpiry,
+		Mac:         []byte{1, 2, 3},
 	}
-	hf.Write(raw)
-	return raw
-}
-
-func allocHopEntry(inIA, outIA addr.IA, hopF common.RawBytes) *seg.HopEntry {
+	hopF.Write(raw)
 	return &seg.HopEntry{
 		RawInIA:     inIA.IAInt(),
 		RawOutIA:    outIA.IAInt(),
-		RawHopField: hopF,
+		RawHopField: raw,
+		HopField: seg.HopField{
+			ExpTime:     uint8(hopF.ExpTime),
+			ConsIngress: uint16(hopF.ConsIngress),
+			ConsEgress:  uint16(hopF.ConsEgress),
+			MAC:         hopF.Mac,
+		},
 	}
 }
diff --git a/go/cs/beaconing/extender_test.go b/go/cs/beaconing/extender_test.go
index 3e2c4b495..d38fd1599 100644
--- a/go/cs/beaconing/extender_test.go
+++ b/go/cs/beaconing/extender_test.go
@@ -21,6 +21,7 @@ import (
 	"crypto/rand"
 	"errors"
 	"hash"
+	mrand "math/rand"
 	"testing"
 	"time"
 
@@ -55,60 +56,60 @@ func TestLegacyExtenderExtend(t *testing.T) {
 	tests := []struct {
 		name          string
 		seg           []common.IFIDType
-		inIfid        common.IFIDType
-		egIfid        common.IFIDType
+		ingress       common.IFIDType
+		egress        common.IFIDType
 		inactivePeers []common.IFIDType
 		errAssertion  assert.ErrorAssertionFunc
 	}{
 		{
-			name:         "First hop, InIfid 0",
-			egIfid:       graph.If_111_A_112_X,
+			name:         "First hop, ingress 0",
+			egress:       graph.If_111_A_112_X,
 			errAssertion: assert.NoError,
 		},
 		{
-			name:         "First hop, EgIfid 0",
-			inIfid:       graph.If_111_B_120_X,
+			name:         "First hop, egress 0",
+			ingress:      graph.If_111_B_120_X,
 			errAssertion: assert.Error,
 		},
 		{
-			name:         "First hop, InIfid 0, EgIfid 0",
+			name:         "First hop, ingress 0, egress 0",
 			errAssertion: assert.Error,
 		},
 		{
 			name:         "First hop, both set",
-			inIfid:       graph.If_111_B_120_X,
-			egIfid:       graph.If_111_A_112_X,
+			ingress:      graph.If_111_B_120_X,
+			egress:       graph.If_111_A_112_X,
 			errAssertion: assert.Error,
 		},
 		{
-			name:         "Second hop, InIfid 0",
+			name:         "Second hop, ingress 0",
 			seg:          segDesc,
-			egIfid:       graph.If_111_A_112_X,
+			egress:       graph.If_111_A_112_X,
 			errAssertion: assert.Error,
 		},
 		{
-			name:         "Second hop, EgIfid 0",
+			name:         "Second hop, egress 0",
 			seg:          segDesc,
-			inIfid:       graph.If_111_B_120_X,
+			ingress:      graph.If_111_B_120_X,
 			errAssertion: assert.NoError,
 		},
 		{
-			name:         "Second hop, InIfid 0, EgIfid 0",
+			name:         "Second hop, ingress 0, egress 0",
 			seg:          segDesc,
 			errAssertion: assert.Error,
 		},
 		{
 			name:         "Second hop, both set",
 			seg:          segDesc,
-			inIfid:       graph.If_111_B_120_X,
-			egIfid:       graph.If_111_A_112_X,
+			ingress:      graph.If_111_B_120_X,
+			egress:       graph.If_111_A_112_X,
 			errAssertion: assert.NoError,
 		},
 		{
 			name:          "Ignore provided, but inactive peer",
 			seg:           segDesc,
-			inIfid:        graph.If_111_B_120_X,
-			egIfid:        graph.If_111_A_112_X,
+			ingress:       graph.If_111_B_120_X,
+			egress:        graph.If_111_A_112_X,
 			inactivePeers: []common.IFIDType{graph.If_111_B_211_A},
 			errAssertion:  assert.NoError,
 		},
@@ -132,21 +133,37 @@ func TestLegacyExtenderExtend(t *testing.T) {
 				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
 				StaticInfo: func() *StaticInfoCfg { return nil },
 			}
+
+			ts := util.TimeToSecs(time.Now())
+			rawInfo := make([]byte, spath.InfoFieldLength)
+			(&spath.InfoField{
+				ISD:   1,
+				TsInt: ts,
+			}).Write(rawInfo)
+
+			pseg, err := seg.NewSeg(
+				&seg.PathSegmentSignedData{
+					RawInfo:      rawInfo,
+					RawTimestamp: ts,
+					SegID:        uint16(mrand.Int()),
+				},
+			)
+			require.NoError(t, err)
+
 			// Create path segment from description, if available.
-			pseg, err := seg.NewSeg(&spath.InfoField{ISD: 1, TsInt: util.TimeToSecs(time.Now())})
 			if len(test.seg) > 0 {
 				pseg = testBeacon(g, test.seg).Segment
 			}
 			require.NoError(t, err)
 			// Extend the segment.
 			err = ext.Extend(context.Background(), pseg,
-				test.inIfid, test.egIfid, append(test.inactivePeers, peer))
+				test.ingress, test.egress, append(test.inactivePeers, peer))
 			test.errAssertion(t, err)
 			if err != nil {
 				return
 			}
 
-			if test.egIfid == 0 {
+			if test.egress == 0 {
 				assert.NoError(t, pseg.Validate(seg.ValidateSegment))
 			} else {
 				assert.NoError(t, pseg.Validate(seg.ValidateBeacon))
@@ -164,24 +181,23 @@ func TestLegacyExtenderExtend(t *testing.T) {
 				// Checks that inactive peers are ignored, even when provided.
 				assert.Len(t, entry.HopEntries, 2)
 			})
-			infoF, err := pseg.InfoF()
-			require.NoError(t, err)
 
+			infoTS := pseg.Timestamp()
 			t.Run("hop entry check", func(t *testing.T) {
 				var prev []byte
 				// The extended hop entry is not the first one.
 				if pseg.MaxAEIdx() > 0 {
 					prev = pseg.ASEntries[pseg.MaxAEIdx()-1].HopEntries[0].RawHopField
 				}
-				testHopEntry(t, entry.HopEntries[0], intfs, test.inIfid, test.egIfid)
-				testHopF(t, entry.HopEntries[0], mac, infoF.TsInt, ext.MaxExpTime(),
-					test.inIfid, test.egIfid, prev)
+				testHopEntry(t, entry.HopEntries[0], intfs, test.ingress, test.egress)
+				testHopF(t, entry.HopEntries[0], mac, infoTS, ext.MaxExpTime(),
+					test.ingress, test.egress, prev)
 			})
 
 			t.Run("peer entry is correct", func(t *testing.T) {
-				testHopEntry(t, entry.HopEntries[1], intfs, peer, test.egIfid)
-				testHopF(t, entry.HopEntries[1], mac, infoF.TsInt, ext.MaxExpTime(),
-					peer, test.egIfid, entry.HopEntries[0].RawHopField)
+				testHopEntry(t, entry.HopEntries[1], intfs, peer, test.egress)
+				testHopF(t, entry.HopEntries[1], mac, infoTS, ext.MaxExpTime(),
+					peer, test.egress, entry.HopEntries[0].RawHopField)
 			})
 		})
 	}
@@ -205,9 +221,8 @@ func TestLegacyExtenderExtend(t *testing.T) {
 		pseg := testBeacon(g, segDesc).Segment
 		err = ext.Extend(context.Background(), pseg, graph.If_111_B_120_X, 0, []common.IFIDType{})
 		require.NoError(t, err)
-		hopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
-		require.NoError(t, err)
-		assert.Equal(t, spath.ExpTimeType(1), hopF.ExpTime)
+		hopF := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField
+		assert.Equal(t, uint8(1), hopF.ExpTime)
 
 	})
 	t.Run("segment is not extended on error", func(t *testing.T) {
@@ -215,55 +230,55 @@ func TestLegacyExtenderExtend(t *testing.T) {
 			return testSigner(t, priv, topoProvider.Get().IA())
 		}
 		tests := map[string]struct {
-			Signer         func(t *testing.T) ctrl.Signer
-			InIfID, EgIfID common.IFIDType
-			Activate       func(intfs *ifstate.Interfaces)
+			Signer          func(t *testing.T) ctrl.Signer
+			Ingress, Egress common.IFIDType
+			Activate        func(intfs *ifstate.Interfaces)
 		}{
-			"Unknown Ingress IFID": {
+			"Unknown Ingress": {
 				Signer:   defaultSigner,
-				InIfID:   10,
+				Ingress:  10,
 				Activate: func(intfs *ifstate.Interfaces) {},
 			},
-			"Inactive Ingress IFID": {
+			"Inactive Ingress": {
 				Signer:   defaultSigner,
-				InIfID:   graph.If_111_B_120_X,
+				Ingress:  graph.If_111_B_120_X,
 				Activate: func(intfs *ifstate.Interfaces) {},
 			},
-			"Invalid Ingress Remote IFID": {
-				Signer: defaultSigner,
-				InIfID: graph.If_111_B_120_X,
+			"Invalid Ingress Remote": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
 				Activate: func(intfs *ifstate.Interfaces) {
 					intfs.Get(graph.If_111_B_120_X).Activate(0)
 				},
 			},
-			"Unknown Egress IFID": {
-				Signer: defaultSigner,
-				InIfID: graph.If_111_B_120_X,
-				EgIfID: 10,
+			"Unknown Egress": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Egress:  10,
 				Activate: func(intfs *ifstate.Interfaces) {
 					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
 				},
 			},
-			"Inactive Egress IFID": {
-				Signer: defaultSigner,
-				InIfID: graph.If_111_B_120_X,
-				EgIfID: graph.If_111_A_112_X,
+			"Inactive Egress": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Egress:  graph.If_111_A_112_X,
 				Activate: func(intfs *ifstate.Interfaces) {
 					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
 				},
 			},
-			"Invalid Egress Remote IFID": {
-				Signer: defaultSigner,
-				InIfID: graph.If_111_B_120_X,
-				EgIfID: graph.If_111_A_112_X,
+			"Invalid Egress Remote": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Egress:  graph.If_111_A_112_X,
 				Activate: func(intfs *ifstate.Interfaces) {
 					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
 					intfs.Get(graph.If_111_A_112_X).Activate(0)
 				},
 			},
 			"Signer fails": {
-				Signer: func(t *testing.T) ctrl.Signer { return &failSigner{} },
-				InIfID: graph.If_111_B_120_X,
+				Signer:  func(t *testing.T) ctrl.Signer { return &failSigner{} },
+				Ingress: graph.If_111_B_120_X,
 				Activate: func(intfs *ifstate.Interfaces) {
 					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
 				},
@@ -288,7 +303,7 @@ func TestLegacyExtenderExtend(t *testing.T) {
 				}
 				pseg := testBeacon(g, segDesc).Segment
 				err = ext.Extend(context.Background(), pseg,
-					test.InIfID, test.EgIfID, []common.IFIDType{})
+					test.Ingress, test.Egress, []common.IFIDType{})
 				assert.Error(t, err)
 			})
 		}
@@ -296,51 +311,291 @@ func TestLegacyExtenderExtend(t *testing.T) {
 }
 
 // testHopF checks whether the hop field in the hop entry contains the expected
-// values. The inIfid and prev are different between that cons and peer hop
+// values. The ingress and prev are different between that cons and peer hop
 // field.
-func testHopF(t *testing.T, hop *seg.HopEntry, mac hash.Hash, ts uint32, expTime spath.ExpTimeType,
-	inIfid, egIfid common.IFIDType, prev []byte) {
+func testHopF(t *testing.T, hop *seg.HopEntry, mac hash.Hash, ts time.Time,
+	expTime spath.ExpTimeType, ingress, egress common.IFIDType, prev []byte) {
 
 	if prev != nil {
 		prev = prev[1:]
 	}
 	hopF, err := spath.HopFFromRaw(hop.RawHopField)
 	require.NoError(t, err)
-	assert.Equal(t, inIfid, hopF.ConsIngress)
-	assert.Equal(t, egIfid, hopF.ConsEgress)
-	assert.NoError(t, hopF.Verify(mac, ts, prev))
+	assert.Equal(t, ingress, hopF.ConsIngress)
+	assert.Equal(t, egress, hopF.ConsEgress)
+	assert.NoError(t, hopF.Verify(mac, util.TimeToSecs(ts), prev))
 	assert.Equal(t, expTime, hopF.ExpTime)
 }
 
 // testHopEntry checks whether the hop entry contains the expected values. The
-// inIfid is different between cons and peer hop entries.
+// ingress is different between cons and peer hop entries.
 func testHopEntry(t *testing.T, hop *seg.HopEntry, intfs *ifstate.Interfaces,
-	inIfid, egIfid common.IFIDType) {
+	ingress, egress common.IFIDType) {
 
-	ia, ifid, mtu := addr.IA{}, common.IFIDType(0), uint16(0)
+	ia, remote, mtu := addr.IA{}, common.IFIDType(0), uint16(0)
 	// Hop entries that are not first on the segment, must not
 	// contain zero values.
-	if inIfid != 0 {
-		intf := intfs.Get(inIfid)
+	if ingress != 0 {
+		intf := intfs.Get(ingress)
 		if assert.NotNil(t, intf) {
 			ia = intf.TopoInfo().IA
-			ifid = intf.TopoInfo().RemoteIFID
+			remote = intf.TopoInfo().RemoteIFID
 			mtu = uint16(intf.TopoInfo().MTU)
 		}
 	}
 	assert.Equal(t, ia, hop.InIA())
-	assert.Equal(t, ifid, hop.RemoteInIF)
+	assert.Equal(t, remote, hop.RemoteInIF)
 	assert.Equal(t, mtu, hop.InMTU)
-	ia, ifid = addr.IA{}, common.IFIDType(0)
+	ia, remote = addr.IA{}, common.IFIDType(0)
 	// Hop entries that are not last on the segment, must not
 	// contain zero values.
-	if egIfid != 0 {
-		intf := intfs.Get(egIfid)
+	if egress != 0 {
+		intf := intfs.Get(egress)
 		ia = intf.TopoInfo().IA
-		ifid = intf.TopoInfo().RemoteIFID
+		remote = intf.TopoInfo().RemoteIFID
 	}
 	assert.Equal(t, ia, hop.OutIA())
-	assert.Equal(t, ifid, hop.RemoteOutIF)
+	assert.Equal(t, remote, hop.RemoteOutIF)
+}
+
+func TestDefaultExtenderExtend(t *testing.T) {
+	topoProvider := itopotest.TopoProviderFromFile(t, topoNonCore)
+
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	require.NoError(t, err)
+	pub := priv.Public()
+
+	peer := graph.If_111_C_121_X
+	testsCases := map[string]struct {
+		ingress      common.IFIDType
+		egress       common.IFIDType
+		unsetPeers   []common.IFIDType
+		errAssertion assert.ErrorAssertionFunc
+	}{
+		"valid": {
+			egress:       graph.If_111_A_112_X,
+			errAssertion: assert.NoError,
+		},
+		"ignore unset peers": {
+			egress:       graph.If_111_A_112_X,
+			unsetPeers:   []common.IFIDType{graph.If_111_B_211_A},
+			errAssertion: assert.NoError,
+		},
+		"egress 0": {
+			ingress:      graph.If_111_B_120_X,
+			errAssertion: assert.Error,
+		},
+		"ingress and egress 0": {
+			errAssertion: assert.Error,
+		},
+		"ingress 0": {
+			ingress:      graph.If_111_B_120_X,
+			egress:       graph.If_111_A_112_X,
+			errAssertion: assert.Error,
+		},
+	}
+	for name, tc := range testsCases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			mctrl := gomock.NewController(t)
+			defer mctrl.Finish()
+			// Setup interfaces with active parent, child and one peer interface.
+			intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
+			intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
+			intfs.Get(graph.If_111_A_112_X).Activate(graph.If_112_X_111_A)
+			intfs.Get(peer).Activate(graph.If_121_X_111_C)
+			intfs.Get(peer).SetState(ifstate.Revoked)
+			ext := &DefaultExtender{
+				IA:     topoProvider.Get().IA(),
+				Signer: testSigner(t, priv, topoProvider.Get().IA()),
+				MAC: func() hash.Hash {
+					mac, err := scrypto.InitMac(make([]byte, 16))
+					require.NoError(t, err)
+					return mac
+				},
+				Intfs:      intfs,
+				MTU:        1337,
+				MaxExpTime: func() uint8 { return uint8(beacon.DefaultMaxExpTime) },
+				StaticInfo: func() *StaticInfoCfg { return nil },
+			}
+			pseg, err := seg.NewSeg(
+				&seg.PathSegmentSignedData{
+					RawTimestamp: util.TimeToSecs(time.Now()),
+					SegID:        uint16(mrand.Int()),
+				},
+			)
+			require.NoError(t, err)
+
+			// Extend the segment.
+			err = ext.Extend(context.Background(), pseg, tc.ingress, tc.egress,
+				append(tc.unsetPeers, peer))
+			tc.errAssertion(t, err)
+			if err != nil {
+				return
+			}
+			assert.NoError(t, pseg.Validate(seg.ValidateBeacon))
+
+			err = pseg.VerifyASEntry(context.Background(), segVerifier{pubKey: pub}, 0)
+			require.NoError(t, err)
+
+			entry := pseg.ASEntries[0]
+			t.Run("AS entry", func(t *testing.T) {
+				assert.Equal(t, uint8(16), entry.IfIDSize)
+				assert.Equal(t, uint16(1337), entry.MTU)
+				assert.Equal(t, topoProvider.Get().IA(), entry.IA())
+				// Checks that unset peers are ignored, even when provided.
+				assert.Len(t, entry.HopEntries, 2)
+			})
+			t.Run("hop entry check", func(t *testing.T) {
+				intf := intfs.Get(tc.egress)
+				ia := intf.TopoInfo().IA
+				remote := intf.TopoInfo().RemoteIFID
+				assert.Equal(t, ia, entry.HopEntries[0].OutIA())
+				assert.Equal(t, remote, entry.HopEntries[0].RemoteOutIF)
+
+				assert.Equal(t, uint16(tc.ingress), entry.HopEntries[0].HopField.ConsIngress)
+				assert.Equal(t, uint16(tc.egress), entry.HopEntries[0].HopField.ConsEgress)
+				assert.Equal(t, ext.MaxExpTime(), entry.HopEntries[0].HopField.ExpTime)
+				// FIXME(roosd): Check hop field can be authenticated.
+			})
+			t.Run("peer entry check", func(t *testing.T) {
+				intf := intfs.Get(tc.egress)
+				ia := intf.TopoInfo().IA
+				remote := intf.TopoInfo().RemoteIFID
+				assert.Equal(t, ia, entry.HopEntries[1].OutIA())
+				assert.Equal(t, remote, entry.HopEntries[1].RemoteOutIF)
+
+				assert.Equal(t, uint16(peer), entry.HopEntries[1].HopField.ConsIngress)
+				assert.Equal(t, uint16(tc.egress), entry.HopEntries[1].HopField.ConsEgress)
+				assert.Equal(t, ext.MaxExpTime(), entry.HopEntries[1].HopField.ExpTime)
+				// FIXME(roosd): Check hop field can be authenticated.
+			})
+		})
+	}
+	t.Run("the maximum expiration time is respected", func(t *testing.T) {
+		mctrl := gomock.NewController(t)
+		defer mctrl.Finish()
+		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
+		require.NoError(t, err)
+		intfs.Get(graph.If_111_A_112_X).Activate(graph.If_112_X_111_A)
+		ext := &DefaultExtender{
+			IA:     topoProvider.Get().IA(),
+			Signer: testSigner(t, priv, topoProvider.Get().IA()),
+			MAC: func() hash.Hash {
+				mac, err := scrypto.InitMac(make([]byte, 16))
+				require.NoError(t, err)
+				return mac
+			},
+			Intfs:      intfs,
+			MTU:        1337,
+			MaxExpTime: func() uint8 { return 1 },
+			StaticInfo: func() *StaticInfoCfg { return nil },
+		}
+		require.NoError(t, err)
+		pseg, err := seg.NewSeg(
+			&seg.PathSegmentSignedData{
+				RawTimestamp: util.TimeToSecs(time.Now()),
+				SegID:        uint16(mrand.Int()),
+			},
+		)
+		require.NoError(t, err)
+		err = ext.Extend(context.Background(), pseg, 0, graph.If_111_A_112_X, []common.IFIDType{})
+		require.NoError(t, err)
+		assert.Equal(t, uint8(1), pseg.ASEntries[0].HopEntries[0].HopField.ExpTime)
+
+	})
+	t.Run("segment is not extended on error", func(t *testing.T) {
+		defaultSigner := func(t *testing.T) ctrl.Signer {
+			return testSigner(t, priv, topoProvider.Get().IA())
+		}
+		testCases := map[string]struct {
+			Signer          func(t *testing.T) ctrl.Signer
+			Ingress, Egress common.IFIDType
+			Activate        func(intfs *ifstate.Interfaces)
+		}{
+			"Unknown Ingress": {
+				Signer:   defaultSigner,
+				Ingress:  10,
+				Activate: func(intfs *ifstate.Interfaces) {},
+			},
+			"Inactive Ingress": {
+				Signer:   defaultSigner,
+				Ingress:  graph.If_111_B_120_X,
+				Activate: func(intfs *ifstate.Interfaces) {},
+			},
+			"Invalid Ingress Remote": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Activate: func(intfs *ifstate.Interfaces) {
+					intfs.Get(graph.If_111_B_120_X).Activate(0)
+				},
+			},
+			"Unknown Egress": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Egress:  10,
+				Activate: func(intfs *ifstate.Interfaces) {
+					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
+				},
+			},
+			"Inactive Egress": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Egress:  graph.If_111_A_112_X,
+				Activate: func(intfs *ifstate.Interfaces) {
+					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
+				},
+			},
+			"Invalid Egress Remote": {
+				Signer:  defaultSigner,
+				Ingress: graph.If_111_B_120_X,
+				Egress:  graph.If_111_A_112_X,
+				Activate: func(intfs *ifstate.Interfaces) {
+					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
+					intfs.Get(graph.If_111_A_112_X).Activate(0)
+				},
+			},
+			"Signer fails": {
+				Signer:  func(t *testing.T) ctrl.Signer { return &failSigner{} },
+				Ingress: graph.If_111_B_120_X,
+				Activate: func(intfs *ifstate.Interfaces) {
+					intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
+				},
+			},
+		}
+		for name, tc := range testCases {
+			name, tc := name, tc
+			t.Run(name, func(t *testing.T) {
+				mctrl := gomock.NewController(t)
+				defer mctrl.Finish()
+				intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
+				tc.Activate(intfs)
+				ext := &DefaultExtender{
+					IA:     topoProvider.Get().IA(),
+					Signer: testSigner(t, priv, topoProvider.Get().IA()),
+					MAC: func() hash.Hash {
+						mac, err := scrypto.InitMac(make([]byte, 16))
+						require.NoError(t, err)
+						return mac
+					},
+					Intfs:      intfs,
+					MTU:        1337,
+					MaxExpTime: func() uint8 { return uint8(beacon.DefaultMaxExpTime) },
+					StaticInfo: func() *StaticInfoCfg { return nil },
+				}
+				pseg, err := seg.NewSeg(
+					&seg.PathSegmentSignedData{
+						RawTimestamp: util.TimeToSecs(time.Now()),
+						SegID:        uint16(mrand.Int()),
+					},
+				)
+				require.NoError(t, err)
+				err = ext.Extend(context.Background(), pseg, tc.Ingress, tc.Egress,
+					[]common.IFIDType{})
+				assert.Error(t, err)
+			})
+		}
+	})
 }
 
 type failSigner struct {
diff --git a/go/cs/beaconing/originator_test.go b/go/cs/beaconing/originator_test.go
index 22689bcef..374600572 100644
--- a/go/cs/beaconing/originator_test.go
+++ b/go/cs/beaconing/originator_test.go
@@ -99,13 +99,12 @@ func TestOriginatorRun(t *testing.T) {
 					segVerifier{pubKey: pub}, pseg.MaxAEIdx()))
 
 				// Extract the hop field from the current AS entry to compare.
-				hopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
-				require.NoError(t, err)
+				hopF := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField
 				// Check the interface matches.
-				assert.Equal(t, hopF.ConsEgress, egress)
+				assert.Equal(t, hopF.ConsEgress, uint16(egress))
 				// Check that the beacon is sent to the correct border router.
-				brAddress := topoProvider.Get().IFInfoMap()[hopF.ConsEgress].InternalAddr
-				assert.Equal(t, brAddress, nextHop)
+				br := topoProvider.Get().IFInfoMap()[egress].InternalAddr
+				assert.Equal(t, br, nextHop)
 				return nil
 			},
 		)
diff --git a/go/cs/beaconing/propagator_test.go b/go/cs/beaconing/propagator_test.go
index 7bad49080..2df715439 100644
--- a/go/cs/beaconing/propagator_test.go
+++ b/go/cs/beaconing/propagator_test.go
@@ -199,13 +199,13 @@ func TestPropagatorRun(t *testing.T) {
 						segVerifier{pubKey: pub}, pseg.MaxAEIdx()))
 
 					// Extract the hop field from the current AS entry to compare.
-					hopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
+					hopF := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField
 					require.NoError(t, err)
 					// Check the interface matches.
-					assert.Equal(t, hopF.ConsEgress, egress)
+					assert.Equal(t, hopF.ConsEgress, uint16(egress))
 					// Check that the beacon is sent to the correct border router.
-					brAddress := topoProvider.Get().IFInfoMap()[hopF.ConsEgress].InternalAddr
-					assert.Equal(t, brAddress, nextHop)
+					br := topoProvider.Get().IFInfoMap()[egress].InternalAddr
+					assert.Equal(t, br, nextHop)
 					return nil
 				},
 			)
diff --git a/go/hidden_path_srv/internal/hpsegreq/fetcher_test.go b/go/hidden_path_srv/internal/hpsegreq/fetcher_test.go
index 7d66b2c2c..4ae8ff2b1 100644
--- a/go/hidden_path_srv/internal/hpsegreq/fetcher_test.go
+++ b/go/hidden_path_srv/internal/hpsegreq/fetcher_test.go
@@ -352,9 +352,7 @@ func TestFetcher(t *testing.T) {
 func markHidden(t *testing.T, m *seg.Meta) *seg.Meta {
 	t.Helper()
 	s := m.Segment
-	infoF, err := s.SData.InfoF()
-	require.NoError(t, err)
-	newSeg, err := seg.NewSeg(infoF)
+	newSeg, err := seg.NewSeg(s.SData)
 	require.NoError(t, err)
 	if s.MaxAEIdx() < 0 {
 		panic("Segment has no AS entries")
diff --git a/go/hidden_path_srv/internal/registration/validator_test.go b/go/hidden_path_srv/internal/registration/validator_test.go
index 3ae2c53cb..184343928 100644
--- a/go/hidden_path_srv/internal/registration/validator_test.go
+++ b/go/hidden_path_srv/internal/registration/validator_test.go
@@ -188,9 +188,7 @@ func TestValidator(t *testing.T) {
 func markHidden(t *testing.T, m *seg.Meta) *seg.Meta {
 	t.Helper()
 	s := m.Segment
-	infoF, err := s.SData.InfoF()
-	require.NoError(t, err)
-	newSeg, err := seg.NewSeg(infoF)
+	newSeg, err := seg.NewSeg(s.SData)
 	require.NoError(t, err)
 	if s.MaxAEIdx() < 0 {
 		panic("Segment has no AS entries")
diff --git a/go/lib/ctrl/seg/seg_test.go b/go/lib/ctrl/seg/seg_test.go
index 7a4bb907b..5cadce8a8 100644
--- a/go/lib/ctrl/seg/seg_test.go
+++ b/go/lib/ctrl/seg/seg_test.go
@@ -51,6 +51,9 @@ func TestPathSegmentAddASEntry(t *testing.T) {
 					RemoteOutIF: 23,
 					RawOutIA:    as111.IAInt(),
 					RawHopField: make(common.RawBytes, spath.HopFieldLength),
+					HopField: HopField{
+						MAC: make([]byte, 3),
+					},
 				},
 			},
 		},
@@ -67,6 +70,9 @@ func TestPathSegmentAddASEntry(t *testing.T) {
 					RemoteOutIF: 24,
 					RawOutIA:    as112.IAInt(),
 					RawHopField: make(common.RawBytes, spath.HopFieldLength),
+					HopField: HopField{
+						MAC: make([]byte, 3),
+					},
 				},
 			},
 		},
@@ -81,6 +87,9 @@ func TestPathSegmentAddASEntry(t *testing.T) {
 					RemoteInIF:  19,
 					RawInIA:     as111.IAInt(),
 					RawHopField: make(common.RawBytes, spath.HopFieldLength),
+					HopField: HopField{
+						MAC: make([]byte, 3),
+					},
 				},
 			},
 		},
@@ -90,7 +99,13 @@ func TestPathSegmentAddASEntry(t *testing.T) {
 		keyPairs = append(keyPairs, newKeyPair(t))
 	}
 	Convey("When constructing a path segment by adding multiple AS entries", t, func() {
-		pseg, err := NewSeg(&spath.InfoField{ISD: 1, TsInt: 13})
+		rawInfo := make([]byte, spath.InfoFieldLength)
+		(&spath.InfoField{ISD: 1, TsInt: 13}).Write(rawInfo)
+		pseg, err := NewSeg(&PathSegmentSignedData{
+			RawInfo:      make([]byte, spath.InfoFieldLength),
+			RawTimestamp: 13,
+			SegID:        1337,
+		})
 		xtest.FailOnErr(t, err)
 		id, fullId := getIds(t, pseg)
 		for i, entry := range asEntries {
@@ -124,7 +139,13 @@ func TestPathSegmentAddASEntry(t *testing.T) {
 		})
 	})
 	Convey("When adding an AS entry fails, the segment is not affected", t, func() {
-		pseg, err := NewSeg(&spath.InfoField{ISD: 1, TsInt: 13})
+		rawInfo := make([]byte, spath.InfoFieldLength)
+		(&spath.InfoField{ISD: 1, TsInt: 13}).Write(rawInfo)
+		pseg, err := NewSeg(&PathSegmentSignedData{
+			RawInfo:      make([]byte, spath.InfoFieldLength),
+			RawTimestamp: 0,
+			SegID:        1337,
+		})
 		xtest.FailOnErr(t, err)
 		err = pseg.AddASEntry(context.Background(), asEntries[0], keyPairs[0])
 		SoMsg("AddASEntry err", err, ShouldBeNil)
@@ -160,11 +181,7 @@ func TestPathSegmentAddASEntry(t *testing.T) {
 
 func getIds(t *testing.T, seg *PathSegment) (common.RawBytes, common.RawBytes) {
 	t.Helper()
-	id, err := seg.ID()
-	xtest.FailOnErr(t, err)
-	full, err := seg.ID()
-	xtest.FailOnErr(t, err)
-	return id, full
+	return seg.ID(), seg.FullID()
 }
 
 type keyPair struct {
diff --git a/go/lib/ctrl/seg/segs_test.go b/go/lib/ctrl/seg/segs_test.go
index 2934c9c60..f5468d717 100644
--- a/go/lib/ctrl/seg/segs_test.go
+++ b/go/lib/ctrl/seg/segs_test.go
@@ -63,11 +63,13 @@ func allocPathSegment(ctrl *gomock.Controller, ias []addr.IA) *PathSegment {
 			HopEntries: []*HopEntry{allocHopEntry(inIA, outIA, rawHops[i])},
 		}
 	}
-	info := &spath.InfoField{
-		TsInt: uint32(time.Now().Unix()),
-		ISD:   uint16(ias[0].I),
-	}
-	pseg, _ := NewSeg(info)
+	rawInfo := make([]byte, spath.InfoFieldLength)
+	(&spath.InfoField{ISD: uint16(ias[0].I), TsInt: uint32(time.Now().Unix())}).Write(rawInfo)
+	pseg, _ := NewSeg(&PathSegmentSignedData{
+		RawInfo:      make([]byte, spath.InfoFieldLength),
+		RawTimestamp: uint32(time.Now().Unix()),
+		SegID:        1337,
+	})
 	signer := mock_seg.NewMockSigner(ctrl)
 	signer.EXPECT().Sign(gomock.Any(), gomock.AssignableToTypeOf(common.RawBytes{})).Return(
 		&proto.SignS{}, nil).AnyTimes()
diff --git a/go/lib/pathdb/pathdbtest/pathdbtest.go b/go/lib/pathdb/pathdbtest/pathdbtest.go
index aef545879..87e3da441 100644
--- a/go/lib/pathdb/pathdbtest/pathdbtest.go
+++ b/go/lib/pathdb/pathdbtest/pathdbtest.go
@@ -18,6 +18,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
+	mrand "math/rand"
 	"sort"
 	"testing"
 	"time"
@@ -306,12 +307,20 @@ func testUpdateIntfToSeg(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.Re
 	InsertSeg(t, ctx, pathDB, ps, hpCfgIDs)
 	checkInterfacesPresent(t, ctx, ps.ASEntries, pathDB)
 	// Create a new segment with an additional peer entry.
-	info := &spath.InfoField{
-		TsInt: uint32(30),
+
+	rawInfo := make([]byte, spath.InfoFieldLength)
+	(&spath.InfoField{
 		ISD:   1,
-		Hops:  3,
-	}
-	newPs, err := seg.NewSeg(info)
+		TsInt: 30,
+	}).Write(rawInfo)
+
+	newPs, err := seg.NewSeg(
+		&seg.PathSegmentSignedData{
+			RawInfo:      rawInfo,
+			RawTimestamp: 30,
+			SegID:        uint16(mrand.Int()),
+		},
+	)
 	require.NoError(t, err)
 	hfr := make([]byte, 8)
 	hf := spath.HopField{
@@ -336,7 +345,7 @@ func testUpdateIntfToSeg(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.Re
 	ps, _ = AllocPathSegment(t, ctrl, ifs1, uint32(40))
 	InsertSeg(t, ctx, pathDB, ps, hpCfgIDs)
 	checkInterfacesPresent(t, ctx, ps.ASEntries, pathDB)
-	checkInterface(t, ctx, newPs.ASEntries[1].IA(), hf.ConsIngress, pathDB, false)
+	checkInterface(t, ctx, newPs.ASEntries[1].IA(), uint16(hf.ConsIngress), pathDB, false)
 }
 
 func testDeleteExpired(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.ReadWrite) {
@@ -377,9 +386,8 @@ func testGetMixed(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.ReadWrite
 	// Call
 	res, err := pathDB.Get(ctx, params)
 	require.NoError(t, err)
-	resSegID, _ := res[0].Seg.ID()
 	assert.Equal(t, 1, len(res), "Result count")
-	assert.Equal(t, segID1, resSegID, "SegIDs match")
+	assert.Equal(t, segID1, res[0].Seg.ID(), "SegIDs match")
 	assert.Equal(t, proto.PathSegType_up, res[0].Type)
 	checkSameHpCfgs(t, "HpCfgIDs match", res[0].HpCfgIDs, hpCfgIDs)
 }
@@ -399,7 +407,7 @@ func testGetNilParams(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.ReadW
 	assert.Equal(t, 2, len(res), "Result count")
 	for _, r := range res {
 		assert.Equal(t, proto.PathSegType_up, r.Type)
-		resSegID, _ := r.Seg.ID()
+		resSegID := r.Seg.ID()
 		if bytes.Compare(resSegID, segID1) == 0 {
 			checkSameHpCfgs(t, "HpCfgIDs match", r.HpCfgIDs, hpCfgIDs)
 		} else if bytes.Compare(resSegID, segID2) == 0 {
@@ -433,7 +441,7 @@ func testGetAll(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.ReadWrite)
 	for r := range resChan {
 		assert.NoError(t, r.Err)
 		assert.Equal(t, proto.PathSegType_up, r.Result.Type)
-		resSegID, _ := r.Result.Seg.ID()
+		resSegID := r.Result.Seg.ID()
 		if bytes.Compare(resSegID, segID1) == 0 {
 			checkSameHpCfgs(t, "HpCfgIDs match", r.Result.HpCfgIDs, hpCfgIDs)
 		} else if bytes.Compare(resSegID, segID2) == 0 {
@@ -525,15 +533,13 @@ func testGetModifiedIDs(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.Rea
 	res, err = pathDB.Get(ctx, q)
 	require.NoError(t, err)
 	assert.Equal(t, 2, len(res), "Result count")
-	expectID1, err := pseg1.ID()
-	require.NoError(t, err)
-	id1, err := res[0].Seg.ID()
-	require.NoError(t, err)
+
+	expectID1 := pseg1.ID()
+	id1 := res[0].Seg.ID()
 	assert.Equal(t, id1, expectID1, "ID 1")
-	expectedID2, err := pseg2.ID()
-	require.NoError(t, err)
-	id2, err := res[1].Seg.ID()
-	require.NoError(t, err)
+
+	expectedID2 := pseg2.ID()
+	id2 := res[1].Seg.ID()
 	assert.Equal(t, id2, expectedID2, "ID 2")
 }
 
@@ -782,7 +788,7 @@ func testRollback(t *testing.T, ctrl *gomock.Controller, pathDB pathdb.PathDB) {
 }
 
 func AllocPathSegment(t *testing.T, ctrl *gomock.Controller, ifs []uint64,
-	expiration uint32) (*seg.PathSegment, common.RawBytes) {
+	expiration uint32) (*seg.PathSegment, []byte) {
 
 	rawHops := make([][]byte, len(ifs)/2)
 	for i := 0; i < len(ifs)/2; i++ {
@@ -791,6 +797,7 @@ func AllocPathSegment(t *testing.T, ctrl *gomock.Controller, ifs []uint64,
 			ConsIngress: common.IFIDType(ifs[2*i]),
 			ConsEgress:  common.IFIDType(ifs[2*i+1]),
 			ExpTime:     spath.DefaultHopFExpiry,
+			Mac:         []byte{1, 2, 3},
 		}
 		hf.Write(rawHops[i])
 	}
@@ -815,12 +822,19 @@ func AllocPathSegment(t *testing.T, ctrl *gomock.Controller, ifs []uint64,
 			},
 		},
 	}
-	info := &spath.InfoField{
-		TsInt: expiration,
+
+	rawInfo := make([]byte, spath.InfoFieldLength)
+	(&spath.InfoField{
 		ISD:   1,
-		Hops:  3,
-	}
-	pseg, err := seg.NewSeg(info)
+		TsInt: expiration,
+	}).Write(rawInfo)
+	pseg, err := seg.NewSeg(
+		&seg.PathSegmentSignedData{
+			RawInfo:      rawInfo,
+			RawTimestamp: expiration,
+			SegID:        uint16(mrand.Int()),
+		},
+	)
 	require.NoError(t, err)
 	signer := mock_seg.NewMockSigner(ctrl)
 	signer.EXPECT().Sign(gomock.Any(), gomock.AssignableToTypeOf(common.RawBytes{})).Return(
@@ -829,18 +843,24 @@ func AllocPathSegment(t *testing.T, ctrl *gomock.Controller, ifs []uint64,
 		err := pseg.AddASEntry(context.Background(), ase, signer)
 		require.NoError(t, err)
 	}
-	segID, err := pseg.ID()
-	require.NoError(t, err)
-	_, err = pseg.FullId()
-	require.NoError(t, err)
-	return pseg, segID
+	return pseg, pseg.ID()
 }
 
 func allocHopEntry(inIA, outIA addr.IA, hopF common.RawBytes) *seg.HopEntry {
+	parsed, err := spath.HopFFromRaw(hopF)
+	if err != nil {
+		panic(err.Error())
+	}
 	return &seg.HopEntry{
 		RawInIA:     inIA.IAInt(),
 		RawOutIA:    outIA.IAInt(),
 		RawHopField: hopF,
+		HopField: seg.HopField{
+			ExpTime:     uint8(parsed.ExpTime),
+			ConsIngress: uint16(parsed.ConsIngress),
+			ConsEgress:  uint16(parsed.ConsEgress),
+			MAC:         parsed.Mac,
+		},
 	}
 }
 
@@ -856,11 +876,6 @@ func checkResult(t *testing.T, results []*query.Result, expectedSeg *seg.PathSeg
 	hpCfgsIds []*query.HPCfgID) {
 
 	assert.Equal(t, 1, len(results), "Expect one result")
-	// Make sure the segment is properly initialized.
-	_, err := results[0].Seg.ID()
-	require.NoError(t, err)
-	_, err = results[0].Seg.FullId()
-	require.NoError(t, err)
 	assert.Equal(t, expectedSeg, results[0].Seg, "Segment should match")
 	checkSameHpCfgs(t, "HiddenPath Ids should match", results[0].HpCfgIDs, hpCfgsIds)
 }
@@ -879,8 +894,7 @@ func checkInterfacesPresent(t *testing.T, ctx context.Context,
 
 	for _, asEntry := range expectedHopEntries {
 		for _, hopEntry := range asEntry.HopEntries {
-			hof, err := hopEntry.HopField()
-			require.NoError(t, err)
+			hof := hopEntry.HopField
 			if hof.ConsIngress != 0 {
 				checkInterface(t, ctx, asEntry.IA(), hof.ConsIngress, pathDB, true)
 			}
@@ -891,14 +905,14 @@ func checkInterfacesPresent(t *testing.T, ctx context.Context,
 	}
 }
 
-func checkInterface(t *testing.T, ctx context.Context, ia addr.IA, ifId common.IFIDType,
+func checkInterface(t *testing.T, ctx context.Context, ia addr.IA, ifId uint16,
 	pathDB pathdb.ReadWrite, present bool) {
 
 	r, err := pathDB.Get(ctx, &query.Params{
 		Intfs: []*query.IntfSpec{
 			{
 				IA:   ia,
-				IfID: ifId,
+				IfID: common.IFIDType(ifId),
 			},
 		},
 	})
diff --git a/go/lib/xtest/graph/graph.go b/go/lib/xtest/graph/graph.go
index 9620012f1..930d02795 100644
--- a/go/lib/xtest/graph/graph.go
+++ b/go/lib/xtest/graph/graph.go
@@ -24,6 +24,7 @@ package graph
 import (
 	"context"
 	"fmt"
+	"math/rand"
 	"sort"
 	"sync"
 	"time"
@@ -235,11 +236,20 @@ func (g *Graph) beacon(ifids []common.IFIDType, addStaticInfo bool) *seg.PathSeg
 		panic(fmt.Sprintf("%d unknown ifid", ifids[0]))
 	}
 
+	ts := util.TimeToSecs(time.Now())
+	rawInfo := make([]byte, spath.InfoFieldLength)
+	(&spath.InfoField{
+		ISD:   uint16(g.parents[ifids[0]].I),
+		TsInt: ts,
+	}).Write(rawInfo)
+
 	segment, err := seg.NewSeg(
-		&spath.InfoField{
-			ISD:   uint16(g.parents[ifids[0]].I),
-			TsInt: util.TimeToSecs(time.Now()),
-		})
+		&seg.PathSegmentSignedData{
+			RawInfo:      rawInfo,
+			RawTimestamp: ts,
+			SegID:        uint16(rand.Int()),
+		},
+	)
 	if err != nil {
 		panic(err)
 	}
@@ -269,6 +279,7 @@ func (g *Graph) beacon(ifids []common.IFIDType, addStaticInfo bool) *seg.PathSeg
 			ConsIngress: inIF,
 			ConsEgress:  outIF,
 			ExpTime:     spath.DefaultHopFExpiry,
+			Mac:         []byte{1, 2, 3},
 		}
 		hf.Write(b)
 		localHopEntry := &seg.HopEntry{
@@ -278,6 +289,12 @@ func (g *Graph) beacon(ifids []common.IFIDType, addStaticInfo bool) *seg.PathSeg
 			RawOutIA:    outIA.IAInt(),
 			RemoteOutIF: remoteOutIF,
 			RawHopField: b,
+			HopField: seg.HopField{
+				ExpTime:     uint8(spath.DefaultHopFExpiry),
+				ConsIngress: uint16(inIF),
+				ConsEgress:  uint16(outIF),
+				MAC:         []byte{1, 2, 3},
+			},
 		}
 		asEntry.HopEntries = append(asEntry.HopEntries, localHopEntry)
 
@@ -311,6 +328,7 @@ func (g *Graph) beacon(ifids []common.IFIDType, addStaticInfo bool) *seg.PathSeg
 					ConsIngress: peeringLocalIF,
 					ConsEgress:  outIF,
 					ExpTime:     spath.DefaultHopFExpiry,
+					Mac:         []byte{1, 2, 3},
 				}
 				hf.Write(b)
 				peeringRemoteIF := g.links[peeringLocalIF]
@@ -322,6 +340,13 @@ func (g *Graph) beacon(ifids []common.IFIDType, addStaticInfo bool) *seg.PathSeg
 					RawOutIA:    outIA.IAInt(),
 					RemoteOutIF: remoteOutIF,
 					RawHopField: b,
+
+					HopField: seg.HopField{
+						ExpTime:     uint8(spath.DefaultHopFExpiry),
+						ConsIngress: uint16(peeringLocalIF),
+						ConsEgress:  uint16(outIF),
+						MAC:         []byte{1, 2, 3},
+					},
 				}
 				asEntry.HopEntries = append(asEntry.HopEntries, peerHopEntry)
 			}