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 16 "github.com/bluesky-social/indigo/atproto/crypto" 17 "github.com/bluesky-social/indigo/util" 18) 19 20type Client struct { 21 h *http.Client 22 service string 23 pdsHostname string 24 rotationKey *crypto.PrivateKeyK256 25} 26 27type ClientArgs struct { 28 Service string 29 RotationKey []byte 30 PdsHostname string 31} 32 33func NewClient(args *ClientArgs) (*Client, error) { 34 if args.Service == "" { 35 args.Service = "https://plc.directory" 36 } 37 38 rk, err := crypto.ParsePrivateBytesK256([]byte(args.RotationKey)) 39 if err != nil { 40 return nil, err 41 } 42 43 return &Client{ 44 h: util.RobustHTTPClient(), 45 service: args.Service, 46 rotationKey: rk, 47 pdsHostname: args.PdsHostname, 48 }, nil 49} 50 51func (c *Client) CreateDID(ctx context.Context, sigkey *crypto.PrivateKeyK256, recovery string, handle string) (string, *PlcOperation, error) { 52 pubsigkey, err := sigkey.PublicKey() 53 if err != nil { 54 return "", nil, err 55 } 56 57 pubrotkey, err := c.rotationKey.PublicKey() 58 if err != nil { 59 return "", nil, err 60 } 61 62 // todo 63 rotationKeys := []string{pubrotkey.DIDKey()} 64 if recovery != "" { 65 rotationKeys = func(recovery string) []string { 66 newRotationKeys := []string{recovery} 67 for _, k := range rotationKeys { 68 newRotationKeys = append(newRotationKeys, k) 69 } 70 return newRotationKeys 71 }(recovery) 72 } 73 74 op := PlcOperation{ 75 Type: "plc_operation", 76 VerificationMethods: map[string]string{ 77 "atproto": pubsigkey.DIDKey(), 78 }, 79 RotationKeys: rotationKeys, 80 AlsoKnownAs: []string{ 81 "at://" + handle, 82 }, 83 Services: map[string]PlcOperationService{ 84 "atproto_pds": { 85 Type: "AtprotoPersonalDataServer", 86 Endpoint: "https://" + c.pdsHostname, 87 }, 88 }, 89 Prev: nil, 90 } 91 92 signed, err := c.FormatAndSignAtprotoOp(sigkey, op) 93 if err != nil { 94 return "", nil, err 95 } 96 97 did, err := didFromOp(signed) 98 if err != nil { 99 return "", nil, err 100 } 101 102 return did, &op, nil 103} 104 105func didFromOp(op *PlcOperation) (string, error) { 106 b, err := op.MarshalCBOR() 107 if err != nil { 108 return "", err 109 } 110 s := sha256.Sum256(b) 111 b32 := strings.ToLower(base32.StdEncoding.EncodeToString(s[:])) 112 return "did:plc:" + b32[0:24], nil 113} 114 115func (c *Client) FormatAndSignAtprotoOp(sigkey *crypto.PrivateKeyK256, op PlcOperation) (*PlcOperation, error) { 116 b, err := op.MarshalCBOR() 117 if err != nil { 118 return nil, err 119 } 120 121 sig, err := c.rotationKey.HashAndSign(b) 122 if err != nil { 123 return nil, err 124 } 125 126 op.Sig = base64.RawURLEncoding.EncodeToString(sig) 127 128 return &op, nil 129} 130 131func (c *Client) SendOperation(ctx context.Context, did string, op *PlcOperation) error { 132 b, err := json.Marshal(op) 133 if err != nil { 134 return err 135 } 136 137 req, err := http.NewRequestWithContext(ctx, "POST", c.service+"/"+url.QueryEscape(did), bytes.NewBuffer(b)) 138 if err != nil { 139 return err 140 } 141 142 req.Header.Add("content-type", "application/json") 143 144 resp, err := c.h.Do(req) 145 if err != nil { 146 return err 147 } 148 defer resp.Body.Close() 149 150 b, err = io.ReadAll(resp.Body) 151 if err != nil { 152 return fmt.Errorf("error sending operation. status code: %d, response: %s", resp.StatusCode, string(b)) 153 } 154 155 return nil 156}