Monorepo for Tangled
Something went wrong. Try again.
3.2 kB · 123 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124package repodid
import ( "context" "fmt" "net/url" "strings"
atcrypto "github.com/bluesky-social/indigo/atproto/atcrypto" "github.com/did-method-plc/go-didplc/didplc" "tangled.org/core/idresolver")
type PreparedDID struct { RepoDid string SigningKeyRaw []byte op *didplc.RegularOp plcUrl string}
func PrepareRepoDID(plcUrl, knotServiceUrl string) (*PreparedDID, error) { if plcUrl == "" { return nil, fmt.Errorf("PLC directory URL is not configured") } parsed, parseErr := url.Parse(plcUrl) if parseErr != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") { return nil, fmt.Errorf("PLC directory URL is invalid: %q", plcUrl) }
privKey, err := atcrypto.GeneratePrivateKeyK256() if err != nil { return nil, fmt.Errorf("generating signing key: %w", err) }
pubKey, err := privKey.PublicKey() if err != nil { return nil, fmt.Errorf("deriving public key: %w", err) }
didKey := pubKey.DIDKey()
op := didplc.RegularOp{ Type: "plc_operation", RotationKeys: []string{didKey}, VerificationMethods: map[string]string{ "atproto": didKey, }, AlsoKnownAs: []string{}, Services: map[string]didplc.OpService{ "atproto_pds": { Type: "AtprotoPersonalDataServer", Endpoint: knotServiceUrl, }, }, Prev: nil, }
if err := op.Sign(privKey); err != nil { return nil, fmt.Errorf("signing genesis op: %w", err) }
repoDid, err := op.DID() if err != nil { return nil, fmt.Errorf("deriving DID from genesis: %w", err) }
return &PreparedDID{ RepoDid: repoDid, SigningKeyRaw: privKey.Bytes(), op: &op, plcUrl: plcUrl, }, nil}
func (p *PreparedDID) Submit(ctx context.Context) error { plcClient := didplc.Client{ DirectoryURL: p.plcUrl, UserAgent: "tangled-knot", } if err := plcClient.Submit(ctx, p.RepoDid, p.op); err != nil { return fmt.Errorf("submitting to PLC directory: %w", err) } return nil}
const maxDidWebLength = 256
func VerifyRepoDIDWeb(ctx context.Context, resolver *idresolver.Resolver, repoDid, knotServiceUrl string) error { if !strings.HasPrefix(repoDid, "did:web:") { return fmt.Errorf("expected did:web, got: %s", repoDid) }
if len(repoDid) > maxDidWebLength { return fmt.Errorf("did:web exceeds maximum length of %d characters", maxDidWebLength) }
authority := strings.TrimPrefix(repoDid, "did:web:") if colonIdx := strings.IndexByte(authority, ':'); colonIdx >= 0 { authority = authority[:colonIdx] } if authority == "" || strings.ContainsAny(authority, "/#?@ ") { return fmt.Errorf("did:web has invalid authority: %s", repoDid) }
ident, err := resolver.ResolveIdent(ctx, repoDid) if err != nil { return fmt.Errorf("resolving did:web document: %w", err) }
knotEndpoint := ident.GetServiceEndpoint("atproto_pds") if strings.TrimRight(knotEndpoint, "/") != strings.TrimRight(knotServiceUrl, "/") { return fmt.Errorf( "did:web atproto_pds service endpoint %q does not match this knot %q", knotEndpoint, knotServiceUrl, ) }
if _, err := ident.PublicKey(); err != nil { return fmt.Errorf("did:web document missing valid atproto verification method: %w", err) }
return nil}