this repo has no description
1package plc 2 3import ( 4 "bytes" 5 "context" 6 "crypto/sha256" 7 "encoding/base32" 8 "encoding/base64" 9 "encoding/json" 10 "fmt" 11 "io" 12 "net/http" 13 "net/url" 14 "strings" 15 "time" 16 17 "github.com/bluesky-social/indigo/atproto/crypto" 18 "github.com/bluesky-social/indigo/atproto/data" 19 "github.com/bluesky-social/indigo/did" 20 "github.com/bluesky-social/indigo/plc" 21 "github.com/bluesky-social/indigo/util" 22) 23 24type Client struct { 25 plc.CachingDidResolver 26 27 h *http.Client 28 29 service string 30 rotationKey *crypto.PrivateKeyK256 31 recoveryKey string 32 pdsHostname string 33} 34 35type ClientArgs struct { 36 Service string 37 RotationKey []byte 38 RecoveryKey string 39 PdsHostname string 40} 41 42func NewClient(args *ClientArgs) (*Client, error) { 43 if args.Service == "" { 44 args.Service = "https://plc.directory" 45 } 46 47 rk, err := crypto.ParsePrivateBytesK256([]byte(args.RotationKey)) 48 if err != nil { 49 return nil, err 50 } 51 52 resolver := did.NewMultiResolver() 53 return &Client{ 54 CachingDidResolver: *plc.NewCachingDidResolver(resolver, 5*time.Minute, 100_000), 55 h: util.RobustHTTPClient(), 56 service: args.Service, 57 rotationKey: rk, 58 recoveryKey: args.RecoveryKey, 59 pdsHostname: args.PdsHostname, 60 }, nil 61} 62 63func (c *Client) CreateDID(ctx context.Context, sigkey *crypto.PrivateKeyK256, recovery string, handle string) (string, map[string]any, error) { 64 pubrotkey, err := c.rotationKey.PublicKey() 65 if err != nil { 66 return "", nil, err 67 } 68 69 // todo 70 rotationKeys := []string{pubrotkey.DIDKey()} 71 if c.recoveryKey != "" { 72 rotationKeys = []string{c.recoveryKey, rotationKeys[0]} 73 } 74 if recovery != "" { 75 rotationKeys = func(recovery string) []string { 76 newRotationKeys := []string{recovery} 77 for _, k := range rotationKeys { 78 newRotationKeys = append(newRotationKeys, k) 79 } 80 return newRotationKeys 81 }(recovery) 82 } 83 84 op, err := c.FormatAndSignAtprotoOp(sigkey, handle, rotationKeys, nil) 85 if err != nil { 86 return "", nil, err 87 } 88 89 did, err := didForCreateOp(op) 90 if err != nil { 91 return "", nil, err 92 } 93 94 return did, op, nil 95} 96 97func (c *Client) UpdateUserHandle(ctx context.Context, didstr string, nhandle string) error { 98 return nil 99} 100 101func (c *Client) FormatAndSignAtprotoOp(sigkey *crypto.PrivateKeyK256, handle string, rotationKeys []string, prev *string) (map[string]any, error) { 102 pubsigkey, err := sigkey.PublicKey() 103 if err != nil { 104 return nil, err 105 } 106 107 op := map[string]any{ 108 "type": "plc_operation", 109 "verificationMethods": map[string]string{ 110 "atproto": pubsigkey.DIDKey(), 111 }, 112 "rotationKeys": rotationKeys, 113 "alsoKnownAs": []string{"at://" + handle}, 114 "services": map[string]any{ 115 "atproto_pds": map[string]string{ 116 "type": "AtprotoPersonalDataServer", 117 "endpoint": "https://" + c.pdsHostname, 118 }, 119 }, 120 "prev": prev, 121 } 122 123 b, err := data.MarshalCBOR(op) 124 if err != nil { 125 return nil, err 126 } 127 128 sig, err := c.rotationKey.HashAndSign(b) 129 if err != nil { 130 return nil, err 131 } 132 133 op["sig"] = base64.RawURLEncoding.EncodeToString(sig) 134 135 return op, nil 136} 137 138func didForCreateOp(op map[string]any) (string, error) { 139 b, err := data.MarshalCBOR(op) 140 if err != nil { 141 return "", err 142 } 143 144 h := sha256.New() 145 h.Write(b) 146 bs := h.Sum(nil) 147 148 b32 := strings.ToLower(base32.StdEncoding.EncodeToString(bs)) 149 150 return "did:plc:" + b32[0:24], nil 151} 152 153func (c *Client) SendOperation(ctx context.Context, did string, op any) error { 154 b, err := json.Marshal(op) 155 if err != nil { 156 return err 157 } 158 159 req, err := http.NewRequestWithContext(ctx, "POST", c.service+"/"+url.QueryEscape(did), bytes.NewBuffer(b)) 160 if err != nil { 161 return err 162 } 163 164 req.Header.Add("content-type", "application/json") 165 166 resp, err := c.h.Do(req) 167 if err != nil { 168 return err 169 } 170 defer resp.Body.Close() 171 172 fmt.Println(resp.StatusCode) 173 174 b, err = io.ReadAll(resp.Body) 175 if err != nil { 176 return err 177 } 178 179 fmt.Println(string(b)) 180 181 return nil 182}