feat: AuthorityAttestation signatures#15
Conversation
e57487f to
2ee6c0f
Compare
d042656 to
daae365
Compare
98023c8 to
96c9ae8
Compare
96c9ae8 to
0e2bd37
Compare
3a0823b to
1c0dd46
Compare
0e2bd37 to
1466ab7
Compare
1c0dd46 to
fba76d3
Compare
1466ab7 to
d949177
Compare
d949177 to
7bec4bf
Compare
| var _ varsig.SignatureAlgorithm = SignatureAlgorithm{} | ||
|
|
||
| func (SignatureAlgorithm) Code() uint64 { | ||
| return 0x300001 |
There was a problem hiding this comment.
can we make this a named constant? i.e. whats magic about this value?
There was a problem hiding this comment.
+1 with comment to explain multicodec "private use" value.
| func (SignatureAlgorithm) Segments() []uint64 { | ||
| return []uint64{} | ||
| } | ||
|
|
||
| func (SignatureAlgorithm) Decode([]byte) (SignatureAlgorithm, int, error) { | ||
| return SignatureAlgorithm{}, 0, nil | ||
| } | ||
| func (SignatureAlgorithm) Encode() ([]byte, error) { | ||
| return []byte{}, nil | ||
| } |
There was a problem hiding this comment.
Yeah, Segments() should have at minimum the code (0x300001). Encoding/decoding should encode/decode the uvarint for the code.
| alice, err := did.Parse("did:mailto:example.com:alice") | ||
| if err != nil { | ||
| t.Fatalf("failed to parse DID: %v", err) | ||
| } |
There was a problem hiding this comment.
Can we add a test for round tripping, e.g.
t.Run("delegation round-trips through CBOR and verifies", func(t *testing.T) {
encoded, err := delegation.Encode(del)
require.NoError(t, err)
decoded, err := delegation.Decode(encoded)
require.NoError(t, err)
resolver := attested.NewDIDVerifierResolver(authority.Verifier())
v, err := resolver(t.Context(), alice)
require.NoError(t, err)
require.True(t, v.Verify(decoded.SignedBytes(), decoded.Signature().Bytes()))
})There was a problem hiding this comment.
...which should fail right now due to missing encode/decode implementation in signature algorithm.
There was a problem hiding this comment.
Alternatively a varsig encode/decode round trip.
| logger := zaptest.NewLogger(t) | ||
| id := newTestIdentity(t) | ||
|
|
||
| var validationOpts = []validator.Option{ |
There was a problem hiding this comment.
nit: slight preference for validationOpts := ...
| func (SignatureAlgorithm) Segments() []uint64 { | ||
| return []uint64{} | ||
| } | ||
|
|
||
| func (SignatureAlgorithm) Decode([]byte) (SignatureAlgorithm, int, error) { | ||
| return SignatureAlgorithm{}, 0, nil | ||
| } | ||
| func (SignatureAlgorithm) Encode() ([]byte, error) { | ||
| return []byte{}, nil | ||
| } |
There was a problem hiding this comment.
Yeah, Segments() should have at minimum the code (0x300001). Encoding/decoding should encode/decode the uvarint for the code.
There was a problem hiding this comment.
Can we add a test that asserts that the validator will correctly validate an invocation that uses an attested delegation as a proof?
There was a problem hiding this comment.
I think this package needs to live in libforge so that the signature algorithm and resolver can be used in other services.
| var _ varsig.SignatureAlgorithm = SignatureAlgorithm{} | ||
|
|
||
| func (SignatureAlgorithm) Code() uint64 { | ||
| return 0x300001 |
There was a problem hiding this comment.
+1 with comment to explain multicodec "private use" value.
7bec4bf to
e649b65
Compare
_This is part of a set of PRs across the `fil-forge` repos. They all work together using a `go.work`. I'm not sure how to make CI do anything useful, though._ # Attested Signatures + UCAN Principal Clarification This PR (along with coordinated changes in sibling repos) reimplements [attested signatures](fil-one/RFC#7). This was written less thoroughly in [sprue#15](fil-forge/sprue#15), but it needed to be more central for everything in the network to use it. Pulling on that thread unraveled a whole bunch of latent issues with the domain model in UCAN. Nothing fundamentally wrong, just some things that were conflated that are now teased apart to reflect some subtle but important distinctions. ## Attested Signatures To recap: `did:mailto:` DIDs have no native key material. Rather than signing directly, a mailto DID delegates to a trusted **authority** (the signing service), which issues a `/ucan/attest/proof` invocation over a SHA2-256 hash of the message. That invocation *is* the signature. On the verify side, the signature is decoded as an invocation, the hash is checked against the message, and the invocation is validated against the authority. ## DID Documents as First-Class Objects Previously, we "resolved" DIDs directly to verifiers. Now DID documents are a first-class concept in the `did` package, rather than being reimplemented in multiple codebases. The previous code assumed that there was a one-to-one mapping between DIDs and signers/verifiers. That's a natural asssumption when most things are `did:key:`s, where that's true. But in general, a DID document can have multiple `verificationMethod`s, and *that's* what actually maps to a signer/verifier pair. Now we can resolve a DID to a document, and turn a *verification method* within it into a verifier. We can also make a "multi-verifier" (which succeeds if any of them succeed) out of all of the verification methods in the document, or out of just those with a particular verification relationship (notably `CapabilityInvocation` and `CapabilityDelegation`). `did.Document` is now a real typed struct with verification relationships. Resolvers return documents; verifier derivation is a separate step via a pluggable factory registry. This is what makes custom verification method types (like `AuthorityAttestation`) possible. For `did:mailto:`, `didmailto.Resolver` generates synthetic DID documents on-the-fly. Each document contains an `AuthorityAttestation` verification method naming the authority, and the registered factory for that type produces an `AttestedVerifier`. ## UCAN 1.0 Terminology The refactor also tightens up the principal/signing model: - **`Principal`** remains "something with a DID()". - **`Signer`** and **`Verifier`** are no longer `Principal`s. They only deal in signatures. A verification method exists independently of a DID, and a signer or verifier exists independently for the same reason. But they're often tied together, so… - **`Issuer`** is now the noun for "a `Signer` tied to a `Principal`". In many cases, `Issuer` simply replaces the existing use of `Signer`. Notably, there are lot of variables already named `issuer` which were `ucan.Signer` and are now `ucan.Issuer`, which gives me some confidence that this is correct. - **`multikey`** is now a specific family of `Signer`s and `Verifier`s which are based on cryptographic keys which the DID document represents as `Multikey`-type verification methods, specifically Ed25519 and secp256k1 currently. There's a set of enhanced `multikey.Issuer`, `multikey.Signer`, and `multikey.Verifier` types which know about the keys they represent. - **`did:key:`s are not keys** and vice versa. Because of the conflation of signers/verifiers and identities, keys were often represented as the corresponding `did:key:`s. But these are different things: a *key* is a method of signing and verification, while a *DID* is something that identifies a subject. The subject of a `did:key:` is, generally speaking, the entity which holds the private key—which is different from being the key itself! Now that's clearer. - Thus **we no longer "wrap" keys**. Previously, you'd accomplish a `did:web:` signer by creating a `did:key:` signer and wrapping it with a `did:web:` so that it reported that as its DID. Now they're separate concerns. The equivalent of "wrapping" is `multikey.NewIssuer(did.DID, multikey.Signer) multikey.Issuer`. The equivalent of unwrapping is `anIssuer.PrivateKey()`—which returns an actual *key*, not a principal. - We also have `identity.Identity`. This was promoted from `sprue` to solve the same problem in other modules. `Identity` simply wraps an `Issuer` to provide a `DIDDocument()` factory, which can then be used both to serve the DID document on the web and to resolve one's own DID. `Identity` is intended to be used for "our" identity in any given service. The fact that it's such a simple wrapper seems like a smell to me, but it's useful and I didn't want to mess with it too much. It might want a little massaging in the future. ## Questions for Reviewers - For `attested`, the verification needs a context, because it needs to recursively validate the invocation that is the attestation signature. But `Verify()` doesn't take a context right now, so the verifier holds a context given at creation. That's a bit odd. Should `Verify()` change to take a context, or should we keep doing this?
Implements and uses RFC 7 AuthorityAttestation signatures.
Depends on fil-forge/ucantone#28.
PR Dependency Tree
This tree was auto-generated by Charcoal