diff --git a/api/graphql/models/lazy_identity.go b/api/graphql/models/lazy_identity.go
index c19d077bdab4604cbd80182815e689ed2d35845e..efc102a8cc94698101d6e3138d91af03935e2d2e 100644
--- a/api/graphql/models/lazy_identity.go
+++ b/api/graphql/models/lazy_identity.go
@@ -111,10 +111,10 @@ func (li *lazyIdentity) IsProtected() (bool, error) {
 var _ IdentityWrapper = &loadedIdentity{}
 
 type loadedIdentity struct {
-	identity.Interface
+	entity.Identity
 }
 
-func NewLoadedIdentity(id identity.Interface) *loadedIdentity {
+func NewLoadedIdentity(id entity.Identity) *loadedIdentity {
 	return &loadedIdentity{Interface: id}
 }
 
diff --git a/cache/bug_cache.go b/cache/bug_cache.go
index 8f4bca27fdd9cec19a5671496de5006a28bf7f3e..5356fa1ac88d95d8590b0fb328030280d9ad07c7 100644
--- a/cache/bug_cache.go
+++ b/cache/bug_cache.go
@@ -29,7 +29,7 @@ func NewBugCache(b *bug.Bug, repo repository.ClockedRepo, getUserIdentity getUse
 			repo:            repo,
 			entityUpdated:   entityUpdated,
 			getUserIdentity: getUserIdentity,
-			entity:          &withSnapshot[*bug.Snapshot, bug.Operation]{Interface: b},
+			entity:          &withSnapshot[*bug.Snapshot, bug.Operation]{WithCommit: b},
 		},
 	}
 }
@@ -47,7 +47,7 @@ func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash)
 	return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
 }
 
-func (c *BugCache) AddCommentRaw(author entity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *bug.AddCommentOperation, error) {
+func (c *BugCache) AddCommentRaw(author entity.Identity, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *bug.AddCommentOperation, error) {
 	c.mu.Lock()
 	commentId, op, err := bug.AddComment(c.entity, author, unixTime, message, files, metadata)
 	c.mu.Unlock()
@@ -66,7 +66,7 @@ func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelCh
 	return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
 }
 
-func (c *BugCache) ChangeLabelsRaw(author entity.Interface, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
+func (c *BugCache) ChangeLabelsRaw(author entity.Identity, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
 	c.mu.Lock()
 	changes, op, err := bug.ChangeLabels(c.entity, author, unixTime, added, removed, metadata)
 	c.mu.Unlock()
@@ -85,7 +85,7 @@ func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.Lab
 	return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
 }
 
-func (c *BugCache) ForceChangeLabelsRaw(author entity.Interface, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
+func (c *BugCache) ForceChangeLabelsRaw(author entity.Identity, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
 	c.mu.Lock()
 	op, err := bug.ForceChangeLabels(c.entity, author, unixTime, added, removed, metadata)
 	c.mu.Unlock()
@@ -104,7 +104,7 @@ func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
 	return c.OpenRaw(author, time.Now().Unix(), nil)
 }
 
-func (c *BugCache) OpenRaw(author entity.Interface, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
+func (c *BugCache) OpenRaw(author entity.Identity, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
 	c.mu.Lock()
 	op, err := bug.Open(c.entity, author, unixTime, metadata)
 	c.mu.Unlock()
@@ -123,7 +123,7 @@ func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
 	return c.CloseRaw(author, time.Now().Unix(), nil)
 }
 
-func (c *BugCache) CloseRaw(author entity.Interface, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
+func (c *BugCache) CloseRaw(author entity.Identity, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
 	c.mu.Lock()
 	op, err := bug.Close(c.entity, author, unixTime, metadata)
 	c.mu.Unlock()
@@ -142,7 +142,7 @@ func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
 	return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
 }
 
-func (c *BugCache) SetTitleRaw(author entity.Interface, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
+func (c *BugCache) SetTitleRaw(author entity.Identity, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
 	c.mu.Lock()
 	op, err := bug.SetTitle(c.entity, author, unixTime, title, metadata)
 	c.mu.Unlock()
@@ -163,7 +163,7 @@ func (c *BugCache) EditCreateComment(body string) (entity.CombinedId, *bug.EditC
 }
 
 // EditCreateCommentRaw is a convenience function to edit the body of a bug (the first comment)
-func (c *BugCache) EditCreateCommentRaw(author entity.Interface, unixTime int64, body string, metadata map[string]string) (entity.CombinedId, *bug.EditCommentOperation, error) {
+func (c *BugCache) EditCreateCommentRaw(author entity.Identity, unixTime int64, body string, metadata map[string]string) (entity.CombinedId, *bug.EditCommentOperation, error) {
 	c.mu.Lock()
 	commentId, op, err := bug.EditCreateComment(c.entity, author, unixTime, body, nil, metadata)
 	c.mu.Unlock()
@@ -182,7 +182,7 @@ func (c *BugCache) EditComment(target entity.CombinedId, message string) (*bug.E
 	return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
 }
 
-func (c *BugCache) EditCommentRaw(author entity.Interface, unixTime int64, target entity.CombinedId, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
+func (c *BugCache) EditCommentRaw(author entity.Identity, unixTime int64, target entity.CombinedId, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
 	comment, err := c.Compile().SearchComment(target)
 	if err != nil {
 		return nil, err
@@ -209,7 +209,7 @@ func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string)
 	return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
 }
 
-func (c *BugCache) SetMetadataRaw(author entity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
+func (c *BugCache) SetMetadataRaw(author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
 	c.mu.Lock()
 	op, err := bug.SetMetadata(c.entity, author, unixTime, target, newMetadata)
 	c.mu.Unlock()
diff --git a/cache/cached.go b/cache/cached.go
index 99f384878c8111b30ec6d4479b12be855bea3389..d2531f0f2d391d54b657dd1889d5f423a91f7165 100644
--- a/cache/cached.go
+++ b/cache/cached.go
@@ -9,17 +9,17 @@ import (
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
 
-var _ dag.Interface[dag.Snapshot, dag.Operation] = &CachedEntityBase[dag.Snapshot, dag.Operation]{}
-var _ CacheEntity = &CachedEntityBase[dag.Snapshot, dag.Operation]{}
+var _ entity.Interface[entity.Snapshot, dag.Operation] = &CachedEntityBase[entity.Snapshot, dag.Operation]{}
+var _ CacheEntity = &CachedEntityBase[entity.Snapshot, dag.Operation]{}
 
 // CachedEntityBase provide the base function of an entity managed by the cache.
-type CachedEntityBase[SnapT dag.Snapshot, OpT dag.Operation] struct {
+type CachedEntityBase[SnapT entity.Snapshot, OpT dag.Operation] struct {
 	repo            repository.ClockedRepo
 	entityUpdated   func(id entity.Id) error
 	getUserIdentity getUserIdentityFunc
 
 	mu     sync.RWMutex
-	entity dag.Interface[SnapT, OpT]
+	entity entity.WithCommit[SnapT, OpT]
 }
 
 func (e *CachedEntityBase[SnapT, OpT]) Id() entity.Id {
diff --git a/cache/subcache.go b/cache/subcache.go
index c1a550c3cbb9af52d626c0a46c39c8633fce4546..2b0da1a57bf2e40805f2c6eb58a63cdcb79a0766 100644
--- a/cache/subcache.go
+++ b/cache/subcache.go
@@ -10,6 +10,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/entity"
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
@@ -29,17 +30,17 @@ type getUserIdentityFunc func() (*IdentityCache, error)
 // Actions expose a number of action functions on Entities, to give upper layers (cache) a way to normalize interactions.
 // Note: ideally this wouldn't exist, the cache layer would assume that everything is an entity/dag, and directly use the
 // functions from this package, but right now identities are not using that framework.
-type Actions[EntityT entity.Interface] struct {
+type Actions[EntityT entity.Bare] struct {
 	ReadWithResolver    func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (EntityT, error)
-	ReadAllWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[EntityT]
+	ReadAllWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan bootstrap.StreamedEntity[EntityT]
 	Remove              func(repo repository.ClockedRepo, id entity.Id) error
 	RemoveAll           func(repo repository.ClockedRepo) error
-	MergeAll            func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Interface) <-chan entity.MergeResult
+	MergeAll            func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Identity) <-chan entity.MergeResult
 }
 
-var _ cacheMgmt = &SubCache[entity.Interface, Excerpt, CacheEntity]{}
+var _ cacheMgmt = &SubCache[entity.Bare, Excerpt, CacheEntity]{}
 
-type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] struct {
+type SubCache[EntityT entity.Bare, ExcerptT Excerpt, CacheT CacheEntity] struct {
 	repo      repository.ClockedRepo
 	resolvers func() entity.Resolvers
 
@@ -60,7 +61,7 @@ type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] st
 	lru      *lruIdCache
 }
 
-func NewSubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity](
+func NewSubCache[EntityT entity.Bare, ExcerptT Excerpt, CacheT CacheEntity](
 	repo repository.ClockedRepo,
 	resolvers func() entity.Resolvers, getUserIdentity getUserIdentityFunc,
 	makeCached func(entity EntityT, entityUpdated func(id entity.Id) error) CacheT,
diff --git a/cache/with_snapshot.go b/cache/with_snapshot.go
index 674b6923b0f78ea3c3c23c28966fcea8d554423e..47bad74ba913b3586fbfd5cfde238b39f5805147 100644
--- a/cache/with_snapshot.go
+++ b/cache/with_snapshot.go
@@ -3,15 +3,16 @@ package cache
 import (
 	"sync"
 
+	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
-var _ dag.Interface[dag.Snapshot, dag.OperationWithApply[dag.Snapshot]] = &withSnapshot[dag.Snapshot, dag.OperationWithApply[dag.Snapshot]]{}
+var _ entity.Interface[entity.Snapshot, dag.OperationWithApply[entity.Snapshot]] = &withSnapshot[entity.Snapshot, dag.OperationWithApply[entity.Snapshot]]{}
 
 // withSnapshot encapsulate an entity and maintain a snapshot efficiently.
-type withSnapshot[SnapT dag.Snapshot, OpT dag.OperationWithApply[SnapT]] struct {
-	dag.Interface[SnapT, OpT]
+type withSnapshot[SnapT entity.Snapshot, OpT dag.OperationWithApply[SnapT]] struct {
+	entity.WithCommit[SnapT, OpT]
 	mu   sync.Mutex
 	snap *SnapT
 }
@@ -20,7 +21,7 @@ func (ws *withSnapshot[SnapT, OpT]) Compile() SnapT {
 	ws.mu.Lock()
 	defer ws.mu.Unlock()
 	if ws.snap == nil {
-		snap := ws.Interface.Compile()
+		snap := ws.WithCommit.Compile()
 		ws.snap = &snap
 	}
 	return *ws.snap
@@ -31,7 +32,7 @@ func (ws *withSnapshot[SnapT, OpT]) Append(op OpT) {
 	ws.mu.Lock()
 	defer ws.mu.Unlock()
 
-	ws.Interface.Append(op)
+	ws.WithCommit.Append(op)
 
 	if ws.snap == nil {
 		return
@@ -46,7 +47,7 @@ func (ws *withSnapshot[SnapT, OpT]) Commit(repo repository.ClockedRepo) error {
 	ws.mu.Lock()
 	defer ws.mu.Unlock()
 
-	err := ws.Interface.Commit(repo)
+	err := ws.WithCommit.Commit(repo)
 	if err != nil {
 		ws.snap = nil
 		return err
diff --git a/entities/bug/bug_actions.go b/entities/bug/bug_actions.go
index 651d24dc0af1c59a4d997958d5831a03b3b02021..1d1bda78f421c31ddc4a2c0c31106568dcdec432 100644
--- a/entities/bug/bug_actions.go
+++ b/entities/bug/bug_actions.go
@@ -1,7 +1,6 @@
 package bug
 
 import (
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -22,14 +21,14 @@ func Push(repo repository.Repo, remote string) (string, error) {
 // This function will return an error if a merge fail
 // Note: an author is necessary for the case where a merge commit is created, as this commit will
 // have an author and may be signed if a signing key is available.
-func Pull(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) error {
+func Pull(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Identity) error {
 	return dag.Pull(def, wrapper, repo, resolvers, remote, mergeAuthor)
 }
 
 // MergeAll will merge all the available remote bug
 // Note: an author is necessary for the case where a merge commit is created, as this commit will
 // have an author and may be signed if a signing key is available.
-func MergeAll(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult {
+func MergeAll(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Identity) <-chan entity.MergeResult {
 	return dag.MergeAll(def, wrapper, repo, resolvers, remote, mergeAuthor)
 }
 
diff --git a/entities/bug/comment.go b/entities/bug/comment.go
index 7835c5a8a75db094fae4e5b7321c5931aa1016f1..15fb6a5d0c768382a3702093c7bcd5c965042231 100644
--- a/entities/bug/comment.go
+++ b/entities/bug/comment.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"github.com/dustin/go-humanize"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -18,7 +17,7 @@ type Comment struct {
 	// targetId is the Id of the Operation that originally created that Comment
 	targetId entity.Id
 
-	Author  identity.Interface
+	Author  entity.Identity
 	Message string
 	Files   []repository.Hash
 
diff --git a/entities/bug/op_add_comment.go b/entities/bug/op_add_comment.go
index 17cc5dd0575ab8f7496c075009832e851a296b70..833aa894ed7f8459dd9dd22c8b75928ac71ea028 100644
--- a/entities/bug/op_add_comment.go
+++ b/entities/bug/op_add_comment.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -72,7 +71,7 @@ func (op *AddCommentOperation) Validate() error {
 	return nil
 }
 
-func NewAddCommentOp(author identity.Interface, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
+func NewAddCommentOp(author entity.Identity, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
 	return &AddCommentOperation{
 		OpBase:  dag.NewOpBase(AddCommentOp, author, unixTime),
 		Message: message,
@@ -89,7 +88,7 @@ type AddCommentTimelineItem struct {
 func (a *AddCommentTimelineItem) IsAuthored() {}
 
 // AddComment is a convenience function to add a comment to a bug
-func AddComment(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *AddCommentOperation, error) {
+func AddComment(b Interface, author entity.Identity, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *AddCommentOperation, error) {
 	op := NewAddCommentOp(author, unixTime, message, files)
 	for key, val := range metadata {
 		op.SetMetadata(key, val)
diff --git a/entities/bug/op_add_comment_test.go b/entities/bug/op_add_comment_test.go
index fee9e785cb2796cacc6acd299ce8c74d52433a5f..7727c45b975970e73f237f6e85e1b1f4267d0ed6 100644
--- a/entities/bug/op_add_comment_test.go
+++ b/entities/bug/op_add_comment_test.go
@@ -3,17 +3,16 @@ package bug
 import (
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
 func TestAddCommentSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
 		return NewAddCommentOp(author, unixTime, "message", nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
 		return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"}), nil
 	})
 }
diff --git a/entities/bug/op_create.go b/entities/bug/op_create.go
index 63eb438d0e970d8bbc5722827f9110451e8743f6..33504e3d0e58399e1b3f27bdc02495c826424851 100644
--- a/entities/bug/op_create.go
+++ b/entities/bug/op_create.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -89,7 +88,7 @@ func (op *CreateOperation) Validate() error {
 	return nil
 }
 
-func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
+func NewCreateOp(author entity.Identity, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
 	return &CreateOperation{
 		OpBase:  dag.NewOpBase(CreateOp, author, unixTime),
 		Title:   title,
@@ -107,7 +106,7 @@ type CreateTimelineItem struct {
 func (c *CreateTimelineItem) IsAuthored() {}
 
 // Create is a convenience function to create a bug
-func Create(author identity.Interface, unixTime int64, title, message string, files []repository.Hash, metadata map[string]string) (*Bug, *CreateOperation, error) {
+func Create(author entity.Identity, unixTime int64, title, message string, files []repository.Hash, metadata map[string]string) (*Bug, *CreateOperation, error) {
 	b := NewBug()
 	op := NewCreateOp(author, unixTime, title, message, files)
 	for key, val := range metadata {
diff --git a/entities/bug/op_create_test.go b/entities/bug/op_create_test.go
index d8bde46f138c8753436f47812c1b04832637c8a1..f340876fa9164eac7897832e4399275bd98af10a 100644
--- a/entities/bug/op_create_test.go
+++ b/entities/bug/op_create_test.go
@@ -40,10 +40,10 @@ func TestCreate(t *testing.T) {
 }
 
 func TestCreateSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*CreateOperation, entity.Resolvers) {
 		return NewCreateOp(author, unixTime, "title", "message", nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*CreateOperation, entity.Resolvers) {
 		return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"}), nil
 	})
 }
diff --git a/entities/bug/op_edit_comment.go b/entities/bug/op_edit_comment.go
index 788d16d923d1cb75979c6305dc80d3d508402b1d..d2f47db942dfc45608a4537bd4debd4fe4f7de9f 100644
--- a/entities/bug/op_edit_comment.go
+++ b/entities/bug/op_edit_comment.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -107,7 +106,7 @@ func (op *EditCommentOperation) Validate() error {
 	return nil
 }
 
-func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
+func NewEditCommentOp(author entity.Identity, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
 	return &EditCommentOperation{
 		OpBase:  dag.NewOpBase(EditCommentOp, author, unixTime),
 		Target:  target,
@@ -117,7 +116,7 @@ func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.I
 }
 
 // EditComment is a convenience function to apply the operation
-func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
+func EditComment(b Interface, author entity.Identity, unixTime int64, target entity.Id, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
 	op := NewEditCommentOp(author, unixTime, target, message, files)
 	for key, val := range metadata {
 		op.SetMetadata(key, val)
@@ -130,7 +129,7 @@ func EditComment(b Interface, author identity.Interface, unixTime int64, target
 }
 
 // EditCreateComment is a convenience function to edit the body of a bug (the first comment)
-func EditCreateComment(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
+func EditCreateComment(b Interface, author entity.Identity, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
 	createOp := b.FirstOp().(*CreateOperation)
 	return EditComment(b, author, unixTime, createOp.Id(), message, files, metadata)
 }
diff --git a/entities/bug/op_edit_comment_test.go b/entities/bug/op_edit_comment_test.go
index 2ca1345e29fbd569852f8ed23bb769e1925586b9..76a68f305bcacabfd33b54a8359f63a334d8117a 100644
--- a/entities/bug/op_edit_comment_test.go
+++ b/entities/bug/op_edit_comment_test.go
@@ -76,10 +76,10 @@ func TestEdit(t *testing.T) {
 }
 
 func TestEditCommentSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
 		return NewEditCommentOp(author, unixTime, "target", "message", nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
 		return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"}), nil
 	})
 }
diff --git a/entities/bug/op_label_change.go b/entities/bug/op_label_change.go
index 0d13fe9e04a6c32d03a984d25df073dc31bb1434..816effc84146b810f6047592d46ef65d7649003d 100644
--- a/entities/bug/op_label_change.go
+++ b/entities/bug/op_label_change.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -96,7 +95,7 @@ func (op *LabelChangeOperation) Validate() error {
 	return nil
 }
 
-func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
+func NewLabelChangeOperation(author entity.Identity, unixTime int64, added, removed []Label) *LabelChangeOperation {
 	return &LabelChangeOperation{
 		OpBase:  dag.NewOpBase(LabelChangeOp, author, unixTime),
 		Added:   added,
@@ -106,7 +105,7 @@ func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, r
 
 type LabelChangeTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	UnixTime   timestamp.Timestamp
 	Added      []Label
 	Removed    []Label
@@ -120,7 +119,7 @@ func (l LabelChangeTimelineItem) CombinedId() entity.CombinedId {
 func (l *LabelChangeTimelineItem) IsAuthored() {}
 
 // ChangeLabels is a convenience function to change labels on a bug
-func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string, metadata map[string]string) ([]LabelChangeResult, *LabelChangeOperation, error) {
+func ChangeLabels(b Interface, author entity.Identity, unixTime int64, add, remove []string, metadata map[string]string) ([]LabelChangeResult, *LabelChangeOperation, error) {
 	var added, removed []Label
 	var results []LabelChangeResult
 
@@ -186,7 +185,7 @@ func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, r
 // responsible for what you are doing. In the general case, you want to use ChangeLabels instead.
 // The intended use of this function is to allow importers to create legal but unexpected label changes,
 // like removing a label with no information of when it was added before.
-func ForceChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string, metadata map[string]string) (*LabelChangeOperation, error) {
+func ForceChangeLabels(b Interface, author entity.Identity, unixTime int64, add, remove []string, metadata map[string]string) (*LabelChangeOperation, error) {
 	added := make([]Label, len(add))
 	for i, str := range add {
 		added[i] = Label(str)
diff --git a/entities/bug/op_label_change_test.go b/entities/bug/op_label_change_test.go
index e6dc880359ccbcf876fc15c36d09e78224adcd8e..c27e906d642d50a047c695cbbd7e67e11ede703a 100644
--- a/entities/bug/op_label_change_test.go
+++ b/entities/bug/op_label_change_test.go
@@ -3,19 +3,18 @@ package bug
 import (
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
 func TestLabelChangeSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
 		return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"}), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
 		return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
 		return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"}), nil
 	})
 }
diff --git a/entities/bug/op_set_metadata.go b/entities/bug/op_set_metadata.go
index b4aab78c7f30eb1aa1e87a8a0476895238b42693..9d4f01f6dd2b2d71e5234137d1e66613814de7e4 100644
--- a/entities/bug/op_set_metadata.go
+++ b/entities/bug/op_set_metadata.go
@@ -1,17 +1,16 @@
 package bug
 
 import (
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
-func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *dag.SetMetadataOperation[*Snapshot] {
+func NewSetMetadataOp(author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) *dag.SetMetadataOperation[*Snapshot] {
 	return dag.NewSetMetadataOp[*Snapshot](SetMetadataOp, author, unixTime, target, newMetadata)
 }
 
 // SetMetadata is a convenience function to add metadata on another operation
-func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*Snapshot], error) {
+func SetMetadata(b Interface, author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*Snapshot], error) {
 	op := NewSetMetadataOp(author, unixTime, target, newMetadata)
 	if err := op.Validate(); err != nil {
 		return nil, err
diff --git a/entities/bug/op_set_status.go b/entities/bug/op_set_status.go
index 68199129d57d64def78eebf42fe6faeee7732996..0fdce8a6fd0da0d49e6d8b005160dce5cd75cac2 100644
--- a/entities/bug/op_set_status.go
+++ b/entities/bug/op_set_status.go
@@ -4,7 +4,6 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/entities/common"
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -50,7 +49,7 @@ func (op *SetStatusOperation) Validate() error {
 	return nil
 }
 
-func NewSetStatusOp(author identity.Interface, unixTime int64, status common.Status) *SetStatusOperation {
+func NewSetStatusOp(author entity.Identity, unixTime int64, status common.Status) *SetStatusOperation {
 	return &SetStatusOperation{
 		OpBase: dag.NewOpBase(SetStatusOp, author, unixTime),
 		Status: status,
@@ -59,7 +58,7 @@ func NewSetStatusOp(author identity.Interface, unixTime int64, status common.Sta
 
 type SetStatusTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	UnixTime   timestamp.Timestamp
 	Status     common.Status
 }
@@ -72,7 +71,7 @@ func (s SetStatusTimelineItem) CombinedId() entity.CombinedId {
 func (s *SetStatusTimelineItem) IsAuthored() {}
 
 // Open is a convenience function to change a bugs state to Open
-func Open(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
+func Open(b Interface, author entity.Identity, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
 	op := NewSetStatusOp(author, unixTime, common.OpenStatus)
 	for key, value := range metadata {
 		op.SetMetadata(key, value)
@@ -85,7 +84,7 @@ func Open(b Interface, author identity.Interface, unixTime int64, metadata map[s
 }
 
 // Close is a convenience function to change a bugs state to Close
-func Close(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
+func Close(b Interface, author entity.Identity, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
 	op := NewSetStatusOp(author, unixTime, common.ClosedStatus)
 	for key, value := range metadata {
 		op.SetMetadata(key, value)
diff --git a/entities/bug/op_set_status_test.go b/entities/bug/op_set_status_test.go
index 0f6d358a19571923321bb7d7e3f7d33fbbaded01..0670c7cebd4a9196e19e56bc902de85add652a93 100644
--- a/entities/bug/op_set_status_test.go
+++ b/entities/bug/op_set_status_test.go
@@ -4,13 +4,12 @@ import (
 	"testing"
 
 	"github.com/MichaelMure/git-bug/entities/common"
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
 func TestSetStatusSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetStatusOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*SetStatusOperation, entity.Resolvers) {
 		return NewSetStatusOp(author, unixTime, common.ClosedStatus), nil
 	})
 }
diff --git a/entities/bug/op_set_title.go b/entities/bug/op_set_title.go
index 6e445aa6490971bf411f4b2df9bba9003c844a9d..60da2eaec02de4093cd1848e3e80bc45a65a526f 100644
--- a/entities/bug/op_set_title.go
+++ b/entities/bug/op_set_title.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -60,7 +59,7 @@ func (op *SetTitleOperation) Validate() error {
 	return nil
 }
 
-func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
+func NewSetTitleOp(author entity.Identity, unixTime int64, title string, was string) *SetTitleOperation {
 	return &SetTitleOperation{
 		OpBase: dag.NewOpBase(SetTitleOp, author, unixTime),
 		Title:  title,
@@ -70,7 +69,7 @@ func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was
 
 type SetTitleTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	UnixTime   timestamp.Timestamp
 	Title      string
 	Was        string
@@ -84,7 +83,7 @@ func (s SetTitleTimelineItem) CombinedId() entity.CombinedId {
 func (s *SetTitleTimelineItem) IsAuthored() {}
 
 // SetTitle is a convenience function to change a bugs title
-func SetTitle(b Interface, author identity.Interface, unixTime int64, title string, metadata map[string]string) (*SetTitleOperation, error) {
+func SetTitle(b Interface, author entity.Identity, unixTime int64, title string, metadata map[string]string) (*SetTitleOperation, error) {
 	var lastTitleOp *SetTitleOperation
 	for _, op := range b.Operations() {
 		switch op := op.(type) {
diff --git a/entities/bug/op_set_title_test.go b/entities/bug/op_set_title_test.go
index 82425ab49208cb9262a1a18655d2535a279701b0..4d59f4fbd5bc2c76828d08fc853c311c3057c4e9 100644
--- a/entities/bug/op_set_title_test.go
+++ b/entities/bug/op_set_title_test.go
@@ -3,13 +3,12 @@ package bug
 import (
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
 func TestSetTitleSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetTitleOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*SetTitleOperation, entity.Resolvers) {
 		return NewSetTitleOp(author, unixTime, "title", "was"), nil
 	})
 }
diff --git a/entities/bug/snapshot.go b/entities/bug/snapshot.go
index 9dbc78625b09ec2174c1bbb3d1812f94c4d152ab..b458771bc791092b372d3b6adc0d850e4a255f81 100644
--- a/entities/bug/snapshot.go
+++ b/entities/bug/snapshot.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/MichaelMure/git-bug/entities/common"
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -19,9 +18,9 @@ type Snapshot struct {
 	Title        string
 	Comments     []Comment
 	Labels       []Label
-	Author       identity.Interface
-	Actors       []identity.Interface
-	Participants []identity.Interface
+	Author       entity.Identity
+	Actors       []entity.Identity
+	Participants []entity.Identity
 	CreateTime   time.Time
 
 	Timeline []TimelineItem
@@ -94,7 +93,7 @@ func (snap *Snapshot) SearchCommentByOpId(id entity.Id) (*Comment, error) {
 }
 
 // append the operation author to the actors list
-func (snap *Snapshot) addActor(actor identity.Interface) {
+func (snap *Snapshot) addActor(actor entity.Identity) {
 	for _, a := range snap.Actors {
 		if actor.Id() == a.Id() {
 			return
@@ -105,7 +104,7 @@ func (snap *Snapshot) addActor(actor identity.Interface) {
 }
 
 // append the operation author to the participants list
-func (snap *Snapshot) addParticipant(participant identity.Interface) {
+func (snap *Snapshot) addParticipant(participant entity.Identity) {
 	for _, p := range snap.Participants {
 		if participant.Id() == p.Id() {
 			return
diff --git a/entities/bug/timeline.go b/entities/bug/timeline.go
index 84ece2621f85e7589f818f104adedb69ccfb4358..3c425777a4c4af2f7f94165b1a312054af267af6 100644
--- a/entities/bug/timeline.go
+++ b/entities/bug/timeline.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"strings"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -18,7 +17,7 @@ type TimelineItem interface {
 type CommentHistoryStep struct {
 	// The author of the edition, not necessarily the same as the author of the
 	// original comment
-	Author identity.Interface
+	Author entity.Identity
 	// The new message
 	Message  string
 	UnixTime timestamp.Timestamp
@@ -27,7 +26,7 @@ type CommentHistoryStep struct {
 // CommentTimelineItem is a TimelineItem that holds a Comment and its edition history
 type CommentTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	Message    string
 	Files      []repository.Hash
 	CreatedAt  timestamp.Timestamp
diff --git a/entities/identity/common.go b/entities/identity/common.go
index 88e30e3388ac5748036e56df38aef5e871a98d7a..749def6362f00cb020f7ea89b0475c948c060cf1 100644
--- a/entities/identity/common.go
+++ b/entities/identity/common.go
@@ -3,6 +3,8 @@ package identity
 import (
 	"encoding/json"
 	"fmt"
+
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 )
 
 // Custom unmarshaling function to allow package user to delegate
@@ -10,7 +12,7 @@ import (
 // and a Bare.
 //
 // If the given message has a "id" field, it's considered being a proper Identity.
-func UnmarshalJSON(raw json.RawMessage) (Interface, error) {
+func UnmarshalJSON(raw json.RawMessage) (bootstrap.Identity, error) {
 	aux := &IdentityStub{}
 
 	// First try to decode and load as a normal Identity
diff --git a/entities/identity/identity.go b/entities/identity/identity.go
index 9effd14668f6ef32816e2149ea7331013cc644f0..f71d0e4a8da54ba90ce98f02b645e930056a50be 100644
--- a/entities/identity/identity.go
+++ b/entities/identity/identity.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	"github.com/MichaelMure/git-bug/entity"
 	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/lamport"
@@ -28,7 +29,7 @@ var ErrNoIdentitySet = errors.New("No identity is set.\n" +
 	"\"git bug user new\" or adopted with \"git bug user adopt\"")
 var ErrMultipleIdentitiesSet = errors.New("multiple user identities set")
 
-var _ Interface = &Identity{}
+var _ entity.Identity = &Identity{}
 var _ bootstrap.Entity = &Identity{}
 
 type Identity struct {
@@ -40,7 +41,7 @@ func NewIdentity(repo repository.RepoClock, name string, email string) (*Identit
 	return NewIdentityFull(repo, name, email, "", "", nil)
 }
 
-func NewIdentityFull(repo repository.RepoClock, name string, email string, login string, avatarUrl string, keys []*Key) (*Identity, error) {
+func NewIdentityFull(repo repository.RepoClock, name string, email string, login string, avatarUrl string, keys []bootstrap.Key) (*Identity, error) {
 	v, err := newVersion(repo, name, email, login, avatarUrl, keys)
 	if err != nil {
 		return nil, err
@@ -216,13 +217,13 @@ type Mutator struct {
 	Login     string
 	Email     string
 	AvatarUrl string
-	Keys      []*Key
+	Keys      []bootstrap.Key
 }
 
 // Mutate allow to create a new version of the Identity in one go
 func (i *Identity) Mutate(repo repository.RepoClock, f func(orig *Mutator)) error {
-	copyKeys := func(keys []*Key) []*Key {
-		result := make([]*Key, len(keys))
+	copyKeys := func(keys []bootstrap.Key) []bootstrap.Key {
+		result := make([]bootstrap.Key, len(keys))
 		for i, key := range keys {
 			result[i] = key.Clone()
 		}
@@ -466,15 +467,15 @@ func (i *Identity) AvatarUrl() string {
 }
 
 // Keys return the last version of the valid keys
-func (i *Identity) Keys() []*Key {
+func (i *Identity) Keys() []bootstrap.Key {
 	return i.lastVersion().keys
 }
 
 // SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-func (i *Identity) SigningKey(repo repository.RepoKeyring) (*Key, error) {
+func (i *Identity) SigningKey(repo repository.RepoKeyring) (bootstrap.Key, error) {
 	keys := i.Keys()
 	for _, key := range keys {
-		err := key.ensurePrivateKey(repo)
+		err := key.EnsurePrivateKey(repo)
 		if err == errNoPrivateKey {
 			continue
 		}
@@ -487,8 +488,8 @@ func (i *Identity) SigningKey(repo repository.RepoKeyring) (*Key, error) {
 }
 
 // ValidKeysAtTime return the set of keys valid at a given lamport time
-func (i *Identity) ValidKeysAtTime(clockName string, time lamport.Time) []*Key {
-	var result []*Key
+func (i *Identity) ValidKeysAtTime(clockName string, time lamport.Time) []bootstrap.Key {
+	var result []bootstrap.Key
 
 	var lastTime lamport.Time
 	for _, v := range i.versions {
diff --git a/entities/identity/identity_stub.go b/entities/identity/identity_stub.go
index 67a9db5314ac295d1050cb910acb632f25bcf229..94208da10dba352028988463f77f138bbbc84bb6 100644
--- a/entities/identity/identity_stub.go
+++ b/entities/identity/identity_stub.go
@@ -9,7 +9,7 @@ import (
 	"github.com/MichaelMure/git-bug/util/timestamp"
 )
 
-var _ Interface = &IdentityStub{}
+var _ bootstrap.Identity = &IdentityStub{}
 
 // IdentityStub is an almost empty Identity, holding only the id.
 // When a normal Identity is serialized into JSON, only the id is serialized.
@@ -68,15 +68,15 @@ func (IdentityStub) AvatarUrl() string {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
-func (IdentityStub) Keys() []*Key {
+func (IdentityStub) Keys() []bootstrap.Key {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
-func (i *IdentityStub) SigningKey(repo repository.RepoKeyring) (*Key, error) {
+func (i *IdentityStub) SigningKey(repo repository.RepoKeyring) (bootstrap.Key, error) {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
-func (IdentityStub) ValidKeysAtTime(_ string, _ lamport.Time) []*Key {
+func (IdentityStub) ValidKeysAtTime(_ string, _ lamport.Time) []bootstrap.Key {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
diff --git a/entities/identity/identity_test.go b/entities/identity/identity_test.go
index e4ecfbe91bc277b3bc62f9f09763262347a2bed3..3da0c83e7016960aef295b3b029c5098233c287b 100644
--- a/entities/identity/identity_test.go
+++ b/entities/identity/identity_test.go
@@ -37,18 +37,18 @@ func TestIdentityCommitLoad(t *testing.T) {
 
 	// multiple versions
 
-	identity, err = NewIdentityFull(repo, "René Descartes", "rene.descartes@example.com", "", "", []*Key{generatePublicKey()})
+	identity, err = NewIdentityFull(repo, "René Descartes", "rene.descartes@example.com", "", "", []bootstrap.Key{generatePublicKey()})
 	require.NoError(t, err)
 
 	idBeforeCommit = identity.Id()
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
-		orig.Keys = []*Key{generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey()}
 	})
 	require.NoError(t, err)
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
-		orig.Keys = []*Key{generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey()}
 	})
 	require.NoError(t, err)
 
@@ -71,13 +71,13 @@ func TestIdentityCommitLoad(t *testing.T) {
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
 		orig.Email = "rene@descartes.com"
-		orig.Keys = []*Key{generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey()}
 	})
 	require.NoError(t, err)
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
 		orig.Email = "rene@descartes.com"
-		orig.Keys = []*Key{generatePublicKey(), generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey(), generatePublicKey()}
 	})
 	require.NoError(t, err)
 
@@ -134,35 +134,35 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 		versions: []*version{
 			{
 				times: map[string]lamport.Time{"foo": 100},
-				keys:  []*Key{pubKeyA},
+				keys:  []bootstrap.Key{pubKeyA},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 200},
-				keys:  []*Key{pubKeyB},
+				keys:  []bootstrap.Key{pubKeyB},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 201},
-				keys:  []*Key{pubKeyC},
+				keys:  []bootstrap.Key{pubKeyC},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 201},
-				keys:  []*Key{pubKeyD},
+				keys:  []bootstrap.Key{pubKeyD},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 300},
-				keys:  []*Key{pubKeyE},
+				keys:  []bootstrap.Key{pubKeyE},
 			},
 		},
 	}
 
 	require.Nil(t, identity.ValidKeysAtTime("foo", 10))
-	require.Equal(t, identity.ValidKeysAtTime("foo", 100), []*Key{pubKeyA})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 140), []*Key{pubKeyA})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 200), []*Key{pubKeyB})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 201), []*Key{pubKeyD})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 202), []*Key{pubKeyD})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 300), []*Key{pubKeyE})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 3000), []*Key{pubKeyE})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 100), []bootstrap.Key{pubKeyA})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 140), []bootstrap.Key{pubKeyA})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 200), []bootstrap.Key{pubKeyB})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 201), []bootstrap.Key{pubKeyD})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 202), []bootstrap.Key{pubKeyD})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 300), []bootstrap.Key{pubKeyE})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 3000), []bootstrap.Key{pubKeyE})
 }
 
 // Test the immutable or mutable metadata search
@@ -235,7 +235,7 @@ func TestJSON(t *testing.T) {
 	require.NoError(t, err)
 
 	// deserialize, got a IdentityStub with the same id
-	var i Interface
+	var i bootstrap.Identity
 	i, err = UnmarshalJSON(data)
 	require.NoError(t, err)
 	require.Equal(t, identity.Id(), i.Id())
diff --git a/entities/identity/key.go b/entities/identity/key.go
index 87271dd56e1af34e2fd14ab99bf0239a6d30f90d..b9bcc8c3a48fff769e1b991e91f6a26cab4f2d8d 100644
--- a/entities/identity/key.go
+++ b/entities/identity/key.go
@@ -13,6 +13,7 @@ import (
 	"github.com/ProtonMail/go-crypto/openpgp/packet"
 	"github.com/pkg/errors"
 
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
@@ -75,7 +76,7 @@ func (k *Key) Validate() error {
 	return nil
 }
 
-func (k *Key) Clone() *Key {
+func (k *Key) Clone() bootstrap.Key {
 	clone := &Key{}
 
 	pub := *k.public
@@ -185,9 +186,9 @@ func (k *Key) loadPrivate(repo repository.RepoKeyring) error {
 	return nil
 }
 
-// ensurePrivateKey attempt to load the corresponding private key if it is not loaded already.
+// EnsurePrivateKey attempt to load the corresponding private key if it is not loaded already.
 // If no private key is found, returns errNoPrivateKey
-func (k *Key) ensurePrivateKey(repo repository.RepoKeyring) error {
+func (k *Key) EnsurePrivateKey(repo repository.RepoKeyring) error {
 	if k.private != nil {
 		return nil
 	}
diff --git a/entities/identity/key_test.go b/entities/identity/key_test.go
index 6e320dc2056dc35dcc0e4f8de2806a1d277520e9..88301a1e90c724a7a6a58d57eaa30d509e85c7cd 100644
--- a/entities/identity/key_test.go
+++ b/entities/identity/key_test.go
@@ -43,7 +43,7 @@ func TestStoreLoad(t *testing.T) {
 	err = json.Unmarshal(dataJSON, &read)
 	require.NoError(t, err)
 
-	err = read.ensurePrivateKey(repo)
+	err = read.EnsurePrivateKey(repo)
 	require.NoError(t, err)
 
 	require.Equal(t, k.public, read.public)
diff --git a/entities/identity/version.go b/entities/identity/version.go
index 2e30ead731573ecf1d40698519b198b89bc5b656..3f0a22d926fc2d100790a49c3d4e8a260f24a02d 100644
--- a/entities/identity/version.go
+++ b/entities/identity/version.go
@@ -33,7 +33,7 @@ type version struct {
 	// The set of keys valid at that time, from this version onward, until they get removed
 	// in a new version. This allows to have multiple key for the same identity (e.g. one per
 	// device) as well as revoke key.
-	keys []*Key
+	keys []bootstrap.Key
 
 	// mandatory random bytes to ensure a better randomness of the data of the first
 	// version of an identity, used to later generate the ID
@@ -51,7 +51,7 @@ type version struct {
 	commitHash repository.Hash
 }
 
-func newVersion(repo repository.RepoClock, name string, email string, login string, avatarURL string, keys []*Key) (*version, error) {
+func newVersion(repo repository.RepoClock, name string, email string, login string, avatarURL string, keys []bootstrap.Key) (*version, error) {
 	clocks, err := repo.AllClocks()
 	if err != nil {
 		return nil, err
@@ -123,7 +123,7 @@ func (v *version) Clone() *version {
 		clone.times[name] = t
 	}
 
-	clone.keys = make([]*Key, len(v.keys))
+	clone.keys = make([]bootstrap.Key, len(v.keys))
 	for i, key := range v.keys {
 		clone.keys[i] = key.Clone()
 	}
diff --git a/entities/identity/version_test.go b/entities/identity/version_test.go
index 2ad43f3d41c91858fb8274c3ac6baf6715e6f379..3b45ba21f0dfb193a78d35255f5dbaf58ddea00a 100644
--- a/entities/identity/version_test.go
+++ b/entities/identity/version_test.go
@@ -32,7 +32,7 @@ func makeIdentityTestRepo(t *testing.T) repository.ClockedRepo {
 func TestVersionJSON(t *testing.T) {
 	repo := makeIdentityTestRepo(t)
 
-	keys := []*Key{
+	keys := []bootstrap.Key{
 		generatePublicKey(),
 		generatePublicKey(),
 	}
diff --git a/entities/identity/interface.go b/entity/boostrap/identity.go
similarity index 73%
rename from entities/identity/interface.go
rename to entity/boostrap/identity.go
index 88234341b6fe7957554c4e949733fe356805087b..92fb03bf48dd10316f639df6a5e28263121e4ccf 100644
--- a/entities/identity/interface.go
+++ b/entity/boostrap/identity.go
@@ -1,14 +1,32 @@
-package identity
+package bootstrap
 
 import (
-	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
+	"fmt"
+
+	"github.com/ProtonMail/go-crypto/openpgp"
+	"github.com/ProtonMail/go-crypto/openpgp/packet"
+
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/lamport"
 	"github.com/MichaelMure/git-bug/util/timestamp"
 )
 
-type Interface interface {
-	bootstrap.Entity
+var ErrNoPrivateKey = fmt.Errorf("no private key")
+
+type Key interface {
+	Public() *packet.PublicKey
+	Private() *packet.PrivateKey
+	Validate() error
+	Clone() Key
+	PGPEntity() *openpgp.Entity
+
+	// EnsurePrivateKey attempt to load the corresponding private key if it is not loaded already.
+	// If no private key is found, returns ErrNoPrivateKey
+	EnsurePrivateKey(repo repository.RepoKeyring) error
+}
+
+type Identity interface {
+	Entity
 
 	// Name return the last version of the name
 	// Can be empty.
@@ -35,14 +53,14 @@ type Interface interface {
 
 	// Keys return the last version of the valid keys
 	// Can be empty.
-	Keys() []*Key
+	Keys() []Key
 
 	// SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-	SigningKey(repo repository.RepoKeyring) (*Key, error)
+	SigningKey(repo repository.RepoKeyring) (Key, error)
 
 	// ValidKeysAtTime return the set of keys valid at a given lamport time for a given clock of another entity
 	// Can be empty.
-	ValidKeysAtTime(clockName string, time lamport.Time) []*Key
+	ValidKeysAtTime(clockName string, time lamport.Time) []Key
 
 	// LastModification return the timestamp at which the last version of the identity became valid.
 	LastModification() timestamp.Timestamp
diff --git a/entity/dag/common_test.go b/entity/dag/common_test.go
index a5cc4009bea05a1eaf503f34935a35af79c2c5ef..0ca2736daaf7d1bac6f53eec85e6f5a0e37d3276 100644
--- a/entity/dag/common_test.go
+++ b/entity/dag/common_test.go
@@ -30,7 +30,7 @@ type op1 struct {
 	Files  []repository.Hash `json:"files"`
 }
 
-func newOp1(author identity.Interface, field1 string, files ...repository.Hash) *op1 {
+func newOp1(author entity.Identity, field1 string, files ...repository.Hash) *op1 {
 	return &op1{OpBase: NewOpBase(Op1, author, 0), Field1: field1, Files: files}
 }
 
@@ -49,7 +49,7 @@ type op2 struct {
 	Field2 string `json:"field_2"`
 }
 
-func newOp2(author identity.Interface, field2 string) *op2 {
+func newOp2(author entity.Identity, field2 string) *op2 {
 	return &op2{OpBase: NewOpBase(Op2, author, 0), Field2: field2}
 }
 
@@ -103,13 +103,13 @@ func wrapper(e *Entity) *Foo {
   Identities + repo + definition
 */
 
-func makeTestContext() (repository.ClockedRepo, identity.Interface, identity.Interface, entity.Resolvers, Definition) {
+func makeTestContext() (repository.ClockedRepo, entity.Identity, entity.Identity, entity.Resolvers, Definition) {
 	repo := repository.NewMockRepo()
 	id1, id2, resolvers, def := makeTestContextInternal(repo)
 	return repo, id1, id2, resolvers, def
 }
 
-func makeTestContextRemote(t *testing.T) (repository.ClockedRepo, repository.ClockedRepo, repository.ClockedRepo, identity.Interface, identity.Interface, entity.Resolvers, Definition) {
+func makeTestContextRemote(t *testing.T) (repository.ClockedRepo, repository.ClockedRepo, repository.ClockedRepo, entity.Identity, entity.Identity, entity.Resolvers, Definition) {
 	repoA := repository.CreateGoGitTestRepo(t, false)
 	repoB := repository.CreateGoGitTestRepo(t, false)
 	remote := repository.CreateGoGitTestRepo(t, true)
@@ -134,7 +134,7 @@ func makeTestContextRemote(t *testing.T) (repository.ClockedRepo, repository.Clo
 	return repoA, repoB, remote, id1, id2, resolver, def
 }
 
-func makeTestContextInternal(repo repository.ClockedRepo) (identity.Interface, identity.Interface, entity.Resolvers, Definition) {
+func makeTestContextInternal(repo repository.ClockedRepo) (entity.Identity, entity.Identity, entity.Resolvers, Definition) {
 	id1, err := identity.NewIdentity(repo, "name1", "email1")
 	if err != nil {
 		panic(err)
diff --git a/entity/dag/entity.go b/entity/dag/entity.go
index 7c5c4d3c87d4cbc5b0257fd4f181e35b7f496bfd..65dae5040c22fb619459f25455ae9dabaae8bae1 100644
--- a/entity/dag/entity.go
+++ b/entity/dag/entity.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
@@ -455,7 +454,7 @@ func (e *Entity) Commit(repo repository.ClockedRepo) error {
 	}
 
 	for len(e.staging) > 0 {
-		var author identity.Interface
+		var author entity.Identity
 		var toCommit []Operation
 
 		// Split into chunks with the same author
diff --git a/entity/dag/entity_actions.go b/entity/dag/entity_actions.go
index 3c3f819f7a8997b0e2de39c078037f6277ca0e9e..5afca124cd345a3965f0b6adcc59fb7aab25d7de 100644
--- a/entity/dag/entity_actions.go
+++ b/entity/dag/entity_actions.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 )
@@ -32,7 +31,7 @@ func Push(def Definition, repo repository.Repo, remote string) (string, error) {
 
 // Pull will do a Fetch + MergeAll
 // Contrary to MergeAll, this function will return an error if a merge fail.
-func Pull[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) error {
+func Pull[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author entity.Identity) error {
 	_, err := Fetch(def, repo, remote)
 	if err != nil {
 		return err
@@ -68,7 +67,7 @@ func Pull[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT,
 //
 // Note: an author is necessary for the case where a merge commit is created, as this commit will
 // have an author and may be signed if a signing key is available.
-func MergeAll[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) <-chan entity.MergeResult {
+func MergeAll[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author entity.Identity) <-chan entity.MergeResult {
 	out := make(chan entity.MergeResult)
 
 	go func() {
@@ -91,7 +90,7 @@ func MergeAll[EntityT entity.Bare](def Definition, wrapper func(e *Entity) Entit
 
 // merge perform a merge to make sure a local Entity is up-to-date.
 // See MergeAll for more details.
-func merge[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remoteRef string, author identity.Interface) entity.MergeResult {
+func merge[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remoteRef string, author entity.Identity) entity.MergeResult {
 	id := entity.RefToId(remoteRef)
 
 	if err := id.Validate(); err != nil {
diff --git a/entity/dag/example_test.go b/entity/dag/example_test.go
index 3a12db5fd5ce5a406d90c3a850b440718d7fbfae..d56f74a302d55e8c1fd6e8a45ac1f49022b36f86 100644
--- a/entity/dag/example_test.go
+++ b/entity/dag/example_test.go
@@ -30,13 +30,13 @@ import (
 // Snapshot is the compiled view of a ProjectConfig
 type Snapshot struct {
 	// Administrator is the set of users with the higher level of access
-	Administrator map[identity.Interface]struct{}
+	Administrator map[entity.Identity]struct{}
 	// SignatureRequired indicate that all git commit need to be signed
 	SignatureRequired bool
 }
 
 // HasAdministrator returns true if the given identity is included in the administrator.
-func (snap *Snapshot) HasAdministrator(i identity.Interface) bool {
+func (snap *Snapshot) HasAdministrator(i entity.Identity) bool {
 	for admin, _ := range snap.Administrator {
 		if admin.Id() == i.Id() {
 			return true
@@ -78,7 +78,7 @@ type SetSignatureRequired struct {
 	Value bool `json:"value"`
 }
 
-func NewSetSignatureRequired(author identity.Interface, value bool) *SetSignatureRequired {
+func NewSetSignatureRequired(author entity.Identity, value bool) *SetSignatureRequired {
 	return &SetSignatureRequired{
 		OpBase: dag.NewOpBase(SetSignatureRequiredOp, author, time.Now().Unix()),
 		Value:  value,
@@ -106,10 +106,10 @@ func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
 // AddAdministrator is an operation to add a new administrator in the set
 type AddAdministrator struct {
 	dag.OpBase
-	ToAdd []identity.Interface `json:"to_add"`
+	ToAdd []entity.Identity `json:"to_add"`
 }
 
-func NewAddAdministratorOp(author identity.Interface, toAdd ...identity.Interface) *AddAdministrator {
+func NewAddAdministratorOp(author entity.Identity, toAdd ...entity.Identity) *AddAdministrator {
 	return &AddAdministrator{
 		OpBase: dag.NewOpBase(AddAdministratorOp, author, time.Now().Unix()),
 		ToAdd:  toAdd,
@@ -143,10 +143,10 @@ func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
 // RemoveAdministrator is an operation to remove an administrator from the set
 type RemoveAdministrator struct {
 	dag.OpBase
-	ToRemove []identity.Interface `json:"to_remove"`
+	ToRemove []entity.Identity `json:"to_remove"`
 }
 
-func NewRemoveAdministratorOp(author identity.Interface, toRemove ...identity.Interface) *RemoveAdministrator {
+func NewRemoveAdministratorOp(author entity.Identity, toRemove ...entity.Identity) *RemoveAdministrator {
 	return &RemoveAdministrator{
 		OpBase:   dag.NewOpBase(RemoveAdministratorOp, author, time.Now().Unix()),
 		ToRemove: toRemove,
@@ -249,7 +249,7 @@ func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.
 	case *AddAdministrator:
 		// We need to resolve identities
 		for i, stub := range op.ToAdd {
-			iden, err := entity.Resolve[identity.Interface](resolvers, stub.Id())
+			iden, err := entity.Resolve[entity.Identity](resolvers, stub.Id())
 			if err != nil {
 				return nil, err
 			}
@@ -258,7 +258,7 @@ func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.
 	case *RemoveAdministrator:
 		// We need to resolve identities
 		for i, stub := range op.ToRemove {
-			iden, err := entity.Resolve[identity.Interface](resolvers, stub.Id())
+			iden, err := entity.Resolve[entity.Identity](resolvers, stub.Id())
 			if err != nil {
 				return nil, err
 			}
@@ -275,7 +275,7 @@ func (pc ProjectConfig) Compile() *Snapshot {
 	// Note: this would benefit from caching, but it's a simple example
 	snap := &Snapshot{
 		// default value
-		Administrator:     make(map[identity.Interface]struct{}),
+		Administrator:     make(map[entity.Identity]struct{}),
 		SignatureRequired: false,
 	}
 	for _, op := range pc.Operations() {
diff --git a/entity/dag/op_noop.go b/entity/dag/op_noop.go
index d8a2a05aa988435c560636efdc7f6ba825b48ffc..1b600ee0786eb14888592e57bbac296ad96f533b 100644
--- a/entity/dag/op_noop.go
+++ b/entity/dag/op_noop.go
@@ -1,7 +1,6 @@
 package dag
 
 import (
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -15,7 +14,7 @@ type NoOpOperation[SnapT entity.Snapshot] struct {
 	OpBase
 }
 
-func NewNoOpOp[SnapT entity.Snapshot](opType entity.OperationType, author identity.Interface, unixTime int64) *NoOpOperation[SnapT] {
+func NewNoOpOp[SnapT entity.Snapshot](opType entity.OperationType, author entity.Identity, unixTime int64) *NoOpOperation[SnapT] {
 	return &NoOpOperation[SnapT]{
 		OpBase: NewOpBase(opType, author, unixTime),
 	}
diff --git a/entity/dag/op_noop_test.go b/entity/dag/op_noop_test.go
index 61497b5b19b85e1effd791e7e8dc8a3b2951f191..4ce2d19431074a191520e3c9b710ed0299a2a898 100644
--- a/entity/dag/op_noop_test.go
+++ b/entity/dag/op_noop_test.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -13,7 +12,7 @@ func TestNoopSerialize(t *testing.T) {
 		var op NoOpOperation[*snapshotMock]
 		err := json.Unmarshal(raw, &op)
 		return &op, err
-	}, func(author identity.Interface, unixTime int64) (*NoOpOperation[*snapshotMock], entity.Resolvers) {
+	}, func(author entity.Identity, unixTime int64) (*NoOpOperation[*snapshotMock], entity.Resolvers) {
 		return NewNoOpOp[*snapshotMock](1, author, unixTime), nil
 	})
 }
diff --git a/entity/dag/op_set_metadata.go b/entity/dag/op_set_metadata.go
index 191761836d44f38ce2cf661a1f2cb0d57bdba960..7b79a5bf0ffb32aba1d698c7685333d85215cd1e 100644
--- a/entity/dag/op_set_metadata.go
+++ b/entity/dag/op_set_metadata.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/util/text"
 )
@@ -19,7 +18,7 @@ type SetMetadataOperation[SnapT entity.Snapshot] struct {
 	NewMetadata map[string]string `json:"new_metadata"`
 }
 
-func NewSetMetadataOp[SnapT entity.Snapshot](opType entity.OperationType, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation[SnapT] {
+func NewSetMetadataOp[SnapT entity.Snapshot](opType entity.OperationType, author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation[SnapT] {
 	return &SetMetadataOperation[SnapT]{
 		OpBase:      NewOpBase(opType, author, unixTime),
 		Target:      target,
@@ -33,13 +32,16 @@ func (op *SetMetadataOperation[SnapT]) Id() entity.Id {
 
 func (op *SetMetadataOperation[SnapT]) Apply(snapshot SnapT) {
 	for _, target := range snapshot.AllOperations() {
-		if target.Id() == op.Target {
-			// Apply the metadata in an immutable way: if a metadata already
-			// exist, it's not possible to override it.
-			for key, value := range op.NewMetadata {
-				target.setExtraMetadataImmutable(key, value)
+		// cast to dag.Operation to have the private methods
+		if target, ok := target.(Operation); ok {
+			if target.Id() == op.Target {
+				// Apply the metadata in an immutable way: if a metadata already
+				// exist, it's not possible to override it.
+				for key, value := range op.NewMetadata {
+					target.setExtraMetadataImmutable(key, value)
+				}
+				return
 			}
-			return
 		}
 	}
 }
diff --git a/entity/dag/op_set_metadata_test.go b/entity/dag/op_set_metadata_test.go
index 591ce9b27b248846d60b185e15021fdef0e3ca5f..5019d838c4cc8c2e3c1853207bd6e7f8010e9966 100644
--- a/entity/dag/op_set_metadata_test.go
+++ b/entity/dag/op_set_metadata_test.go
@@ -109,7 +109,7 @@ func TestSetMetadataSerialize(t *testing.T) {
 		var op SetMetadataOperation[*snapshotMock]
 		err := json.Unmarshal(raw, &op)
 		return &op, err
-	}, func(author identity.Interface, unixTime int64) (*SetMetadataOperation[*snapshotMock], entity.Resolvers) {
+	}, func(author entity.Identity, unixTime int64) (*SetMetadataOperation[*snapshotMock], entity.Resolvers) {
 		return NewSetMetadataOp[*snapshotMock](1, author, unixTime, "message", map[string]string{
 			"key1": "value1",
 			"key2": "value2",
diff --git a/entity/dag/operation.go b/entity/dag/operation.go
index 40bd7da8d86c9d68107f7575049e8456a7e3a11a..216788c484aae761a9c2382efd055b9e262f1caa 100644
--- a/entity/dag/operation.go
+++ b/entity/dag/operation.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -19,7 +18,7 @@ type Operation interface {
 	// setId allow to set the Id, used when unmarshalling only
 	setId(id entity.Id)
 	// setAuthor allow to set the author, used when unmarshalling only
-	setAuthor(author identity.Interface)
+	setAuthor(author entity.Identity)
 	// setExtraMetadataImmutable add a metadata not carried by the operation itself on the operation
 	setExtraMetadataImmutable(key string, value string)
 }
@@ -36,7 +35,7 @@ type OpBase struct {
 	// Not serialized. Store the op's id in memory.
 	id entity.Id
 	// Not serialized
-	author identity.Interface
+	author entity.Identity
 
 	OperationType entity.OperationType `json:"type"`
 	UnixTime      int64                `json:"timestamp"`
@@ -52,7 +51,7 @@ type OpBase struct {
 	extraMetadata map[string]string
 }
 
-func NewOpBase(opType entity.OperationType, author identity.Interface, unixTime int64) OpBase {
+func NewOpBase(opType entity.OperationType, author entity.Identity, unixTime int64) OpBase {
 	return OpBase{
 		OperationType: opType,
 		author:        author,
@@ -144,7 +143,7 @@ func (base *OpBase) Validate(op entity.Operation, opType entity.OperationType) e
 func (base *OpBase) IsAuthored() {}
 
 // Author return author identity
-func (base *OpBase) Author() identity.Interface {
+func (base *OpBase) Author() entity.Identity {
 	return base.author
 }
 
@@ -204,7 +203,7 @@ func (base *OpBase) setId(id entity.Id) {
 }
 
 // setAuthor allow to set the author, used when unmarshalling only
-func (base *OpBase) setAuthor(author identity.Interface) {
+func (base *OpBase) setAuthor(author entity.Identity) {
 	base.author = author
 }
 
diff --git a/entity/dag/operation_pack.go b/entity/dag/operation_pack.go
index 85cceb4fa6612821635d4e3e07ca94d1cb971054..aaffa03b03740cf51cdb1dc7c2f040d956b0bd06 100644
--- a/entity/dag/operation_pack.go
+++ b/entity/dag/operation_pack.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
@@ -29,7 +30,7 @@ type operationPack struct {
 	id entity.Id
 
 	// The author of the Operations. Must be the same author for all the Operations.
-	Author identity.Interface
+	Author entity.Identity
 	// The list of Operation stored in the operationPack
 	Operations []Operation
 	// Encode the entity's logical time of creation across all entities of the same type.
@@ -58,8 +59,8 @@ func (opp *operationPack) Id() entity.Id {
 
 func (opp *operationPack) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
-		Author     identity.Interface `json:"author"`
-		Operations []Operation        `json:"ops"`
+		Author     entity.Identity `json:"author"`
+		Operations []Operation     `json:"ops"`
 	}{
 		Author:     opp.Author,
 		Operations: opp.Operations,
@@ -235,7 +236,7 @@ func readOperationPack(def Definition, repo repository.RepoData, resolvers entit
 	}
 
 	var id entity.Id
-	var author identity.Interface
+	var author entity.Identity
 	var ops []Operation
 	var createTime lamport.Time
 	var editTime lamport.Time
@@ -323,7 +324,7 @@ func readOperationPackClock(repo repository.RepoData, commit repository.Commit)
 // unmarshallPack delegate the unmarshalling of the Operation's JSON to the decoding
 // function provided by the concrete entity. This gives access to the concrete type of each
 // Operation.
-func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]Operation, identity.Interface, error) {
+func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]Operation, entity.Identity, error) {
 	aux := struct {
 		Author     identity.IdentityStub `json:"author"`
 		Operations []json.RawMessage     `json:"ops"`
@@ -337,7 +338,7 @@ func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]
 		return nil, nil, fmt.Errorf("missing author")
 	}
 
-	author, err := entity.Resolve[identity.Interface](resolvers, aux.Author.Id())
+	author, err := entity.Resolve[entity.Identity](resolvers, aux.Author.Id())
 	if err != nil {
 		return nil, nil, err
 	}
@@ -364,7 +365,7 @@ func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]
 var _ openpgp.KeyRing = &PGPKeyring{}
 
 // PGPKeyring implement a openpgp.KeyRing from an slice of Key
-type PGPKeyring []*identity.Key
+type PGPKeyring []bootstrap.Key
 
 func (pk PGPKeyring) KeysById(id uint64) []openpgp.Key {
 	var result []openpgp.Key
diff --git a/entity/dag/operation_testing.go b/entity/dag/operation_testing.go
index 0ca47d4b5dd065454c11e71e84c699e13256745b..fd4d2343c758b5238429d318027c014505b4270d 100644
--- a/entity/dag/operation_testing.go
+++ b/entity/dag/operation_testing.go
@@ -17,7 +17,7 @@ import (
 func SerializeRoundTripTest[OpT Operation](
 	t *testing.T,
 	unmarshaler OperationUnmarshaler,
-	maker func(author identity.Interface, unixTime int64) (OpT, entity.Resolvers),
+	maker func(author entity.Identity, unixTime int64) (OpT, entity.Resolvers),
 ) {
 	repo := repository.NewMockRepo()
 
diff --git a/entity/identity.go b/entity/identity.go
index 71e0a6e866fc88fb2cec3d409f86d31f757830b4..2523e1f3a2bef63077e161af0b5d066ead939320 100644
--- a/entity/identity.go
+++ b/entity/identity.go
@@ -1,73 +1,9 @@
 package entity
 
-//
-// import (
-// 	"github.com/ProtonMail/go-crypto/openpgp"
-// 	"github.com/ProtonMail/go-crypto/openpgp/packet"
-//
-// 	"github.com/MichaelMure/git-bug/repository"
-// 	"github.com/MichaelMure/git-bug/util/lamport"
-// 	"github.com/MichaelMure/git-bug/util/timestamp"
-// )
-//
-// type Key interface {
-// 	Public() *packet.PublicKey
-// 	Private() *packet.PrivateKey
-// 	Validate() error
-// 	Clone() Key
-// 	PGPEntity() *openpgp.Entity
-// }
-//
-// type Identity interface {
-// 	Bare
-//
-// 	// Name return the last version of the name
-// 	// Can be empty.
-// 	Name() string
-//
-// 	// DisplayName return a non-empty string to display, representing the
-// 	// identity, based on the non-empty values.
-// 	DisplayName() string
-//
-// 	// Email return the last version of the email
-// 	// Can be empty.
-// 	Email() string
-//
-// 	// Login return the last version of the login
-// 	// Can be empty.
-// 	// Warning: this login can be defined when importing from a bridge but should *not* be
-// 	// used to identify an identity as multiple bridge with different login can map to the same
-// 	// identity. Use the metadata system for that usage instead.
-// 	Login() string
-//
-// 	// AvatarUrl return the last version of the Avatar URL
-// 	// Can be empty.
-// 	AvatarUrl() string
-//
-// 	// Keys return the last version of the valid keys
-// 	// Can be empty.
-// 	Keys() []Key
-//
-// 	// SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-// 	SigningKey(repo repository.RepoKeyring) (Key, error)
-//
-// 	// ValidKeysAtTime return the set of keys valid at a given lamport time for a given clock of another entity
-// 	// Can be empty.
-// 	ValidKeysAtTime(clockName string, time lamport.Time) []Key
-//
-// 	// LastModification return the timestamp at which the last version of the identity became valid.
-// 	LastModification() timestamp.Timestamp
-//
-// 	// LastModificationLamports return the lamport times at which the last version of the identity became valid.
-// 	LastModificationLamports() map[string]lamport.Time
-//
-// 	// IsProtected return true if the chain of git commits started to be signed.
-// 	// If that's the case, only signed commit with a valid key for this identity can be added.
-// 	IsProtected() bool
-//
-// 	// Validate check if the Identity data is valid
-// 	Validate() error
-//
-// 	// NeedCommit indicate that the in-memory state changed and need to be committed in the repository
-// 	NeedCommit() bool
-// }
+import bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
+
+var ErrNoPrivateKey = bootstrap.ErrNoPrivateKey
+
+type Key = bootstrap.Key
+
+type Identity = bootstrap.Identity
diff --git a/entity/operations.go b/entity/operations.go
index fd88f033cac7ec895c3e0f3db47e4e53ba3abf3c..4280cf6305cea8e4911f9679a11dfba2989ee9b8 100644
--- a/entity/operations.go
+++ b/entity/operations.go
@@ -3,7 +3,6 @@ package entity
 import (
 	"time"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
@@ -38,7 +37,7 @@ type Operation interface {
 	// Validate check if the Operation data is valid
 	Validate() error
 	// Author returns the author of this operation
-	Author() identity.Interface
+	Author() Identity
 	// Time return the time when the operation was added
 	Time() time.Time
 
diff --git a/misc/random_bugs/create_random_bugs.go b/misc/random_bugs/create_random_bugs.go
index 7e94b61a919d5e294dbebf59d62b7e1947561da9..e88e1054fa49eb32abfc93b13343b031c2538ab8 100644
--- a/misc/random_bugs/create_random_bugs.go
+++ b/misc/random_bugs/create_random_bugs.go
@@ -9,10 +9,11 @@ import (
 
 	"github.com/MichaelMure/git-bug/entities/bug"
 	"github.com/MichaelMure/git-bug/entities/identity"
+	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
-type opsGenerator func(bug.Interface, identity.Interface, int64)
+type opsGenerator func(bug.Interface, entity.Identity, int64)
 
 type Options struct {
 	BugNumber    int
@@ -133,7 +134,7 @@ func generateRandomPersons(repo repository.ClockedRepo, n int) {
 	}
 }
 
-func randomPerson() identity.Interface {
+func randomPerson() entity.Identity {
 	index := rand.Intn(len(persons))
 	return persons[index]
 }
@@ -143,25 +144,25 @@ func paragraphs() string {
 	return strings.Replace(p, "\t", "\n\n", -1)
 }
 
-func comment(b bug.Interface, p identity.Interface, timestamp int64) {
+func comment(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _, _ = bug.AddComment(b, p, timestamp, paragraphs(), nil, nil)
 }
 
-func title(b bug.Interface, p identity.Interface, timestamp int64) {
+func title(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _ = bug.SetTitle(b, p, timestamp, fake.Sentence(), nil)
 }
 
-func open(b bug.Interface, p identity.Interface, timestamp int64) {
+func open(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _ = bug.Open(b, p, timestamp, nil)
 }
 
-func close(b bug.Interface, p identity.Interface, timestamp int64) {
+func close(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _ = bug.Close(b, p, timestamp, nil)
 }
 
 var addedLabels []string
 
-func labels(b bug.Interface, p identity.Interface, timestamp int64) {
+func labels(b bug.Interface, p entity.Identity, timestamp int64) {
 	var removed []string
 	nbRemoved := rand.Intn(3)
 	for nbRemoved > 0 && len(addedLabels) > 0 {