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(sigkey *crypto.PrivateKeyK256, recovery string, handle string) (string, *Operation, 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 := Operation{ 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]OperationService{ 84 "atproto_pds": { 85 Type: "AtprotoPersonalDataServer", 86 Endpoint: "https://" + c.pdsHostname, 87 }, 88 }, 89 Prev: nil, 90 } 91 92 if err := c.SignOp(sigkey, &op); err != nil { 93 return "", nil, err 94 } 95 96 did, err := DidFromOp(&op) 97 if err != nil { 98 return "", nil, err 99 } 100 101 return did, &op, nil 102} 103 104func (c *Client) SignOp(sigkey *crypto.PrivateKeyK256, op *Operation) error { 105 b, err := op.MarshalCBOR() 106 if err != nil { 107 return err 108 } 109 110 sig, err := c.rotationKey.HashAndSign(b) 111 if err != nil { 112 return err 113 } 114 115 op.Sig = base64.RawURLEncoding.EncodeToString(sig) 116 117 return nil 118} 119 120func (c *Client) SendOperation(ctx context.Context, did string, op *Operation) error { 121 b, err := json.Marshal(op) 122 if err != nil { 123 return err 124 } 125 126 req, err := http.NewRequestWithContext(ctx, "POST", c.service+"/"+url.QueryEscape(did), bytes.NewBuffer(b)) 127 if err != nil { 128 return err 129 } 130 131 req.Header.Add("content-type", "application/json") 132 133 resp, err := c.h.Do(req) 134 if err != nil { 135 return err 136 } 137 defer resp.Body.Close() 138 139 b, err = io.ReadAll(resp.Body) 140 if err != nil { 141 return fmt.Errorf("error sending operation. status code: %d, response: %s", resp.StatusCode, string(b)) 142 } 143 144 return nil 145} 146 147func DidFromOp(op *Operation) (string, error) { 148 b, err := op.MarshalCBOR() 149 if err != nil { 150 return "", err 151 } 152 s := sha256.Sum256(b) 153 b32 := strings.ToLower(base32.StdEncoding.EncodeToString(s[:])) 154 return "did:plc:" + b32[0:24], nil 155}