From 8b48846dc1e5c568bdebb0f2251bcac1adef8fca Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 27 May 2026 19:21:17 +0100 Subject: [PATCH] feat: self resolver --- didresolver/self.go | 32 +++++++++++++++++++++ didresolver/self_test.go | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 didresolver/self.go create mode 100644 didresolver/self_test.go diff --git a/didresolver/self.go b/didresolver/self.go new file mode 100644 index 0000000..aa98fa9 --- /dev/null +++ b/didresolver/self.go @@ -0,0 +1,32 @@ +package didresolver + +import ( + "context" + "fmt" + + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/ucan" +) + +type SelfResolver struct { + self did.DID + verifier ucan.Verifier +} + +func (r *SelfResolver) Resolve(_ context.Context, input did.DID) (ucan.Verifier, error) { + if input != r.self { + return nil, fmt.Errorf("not the service's own DID") + } + return r.verifier, nil +} + +// NewSelfResolver returns a DID resolver tier that satisfies requests for the +// service's own DID using the in-memory identity. Returns an error for any +// other DID so a [TieredResolver] falls through to the next tier. +func NewSelfResolver(id principal.Signer) *SelfResolver { + return &SelfResolver{ + self: id.DID(), + verifier: id.Verifier(), + } +} diff --git a/didresolver/self_test.go b/didresolver/self_test.go new file mode 100644 index 0000000..495bbb0 --- /dev/null +++ b/didresolver/self_test.go @@ -0,0 +1,61 @@ +package didresolver_test + +import ( + "testing" + + "github.com/fil-forge/libforge/didresolver" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/principal/signer" + "github.com/stretchr/testify/require" +) + +func TestSelfResolver(t *testing.T) { + // A service identified by a did:web DID backed by an ed25519 key. Wrapping + // the did:key signer makes it announce the did:web DID without changing how + // it signs. + didWeb, err := did.Parse("did:web:example.com") + require.NoError(t, err) + + key, err := ed25519.Generate() + require.NoError(t, err) + + self, err := signer.Wrap(key, didWeb) + require.NoError(t, err) + require.Equal(t, didWeb, self.DID()) + + resolver := didresolver.NewSelfResolver(self) + + t.Run("resolves the service's own did:web without an HTTP request", func(t *testing.T) { + verifier, err := resolver.Resolve(t.Context(), didWeb) + require.NoError(t, err) + + // The resolved verifier announces the requested did:web — not the + // underlying did:key — so token.VerifySignature's issuer-vs-verifier DID + // equality check passes. + require.Equal(t, didWeb, verifier.DID()) + + // It is the service's real verifier: signatures from the signer verify. + msg := []byte("hello") + require.True(t, verifier.Verify(msg, self.Sign(msg))) + }) + + t.Run("does not resolve a different DID so a TieredResolver falls through", func(t *testing.T) { + other, err := did.Parse("did:web:example.org") + require.NoError(t, err) + + verifier, err := resolver.Resolve(t.Context(), other) + require.Error(t, err) + require.Nil(t, verifier) + require.ErrorContains(t, err, "not the service's own DID") + }) + + t.Run("does not resolve the underlying did:key", func(t *testing.T) { + // Only the wrapped did:web identity is served; the did:key the signer + // wraps is a different DID and must not resolve. + verifier, err := resolver.Resolve(t.Context(), key.DID()) + require.Error(t, err) + require.Nil(t, verifier) + require.ErrorContains(t, err, "not the service's own DID") + }) +}