+42
-61
plc/client.go
+42
-61
plc/client.go
···
14
"strings"
15
16
"github.com/bluesky-social/indigo/atproto/crypto"
17
-
"github.com/bluesky-social/indigo/atproto/data"
18
"github.com/bluesky-social/indigo/util"
19
)
20
21
type Client struct {
22
-
h *http.Client
23
-
24
service string
25
rotationKey *crypto.PrivateKeyK256
26
-
recoveryKey string
27
-
pdsHostname string
28
}
29
30
type ClientArgs struct {
31
Service string
32
RotationKey []byte
33
-
RecoveryKey string
34
PdsHostname string
35
}
36
···
48
h: util.RobustHTTPClient(),
49
service: args.Service,
50
rotationKey: rk,
51
-
recoveryKey: args.RecoveryKey,
52
pdsHostname: args.PdsHostname,
53
}, nil
54
}
55
56
-
func (c *Client) CreateDID(ctx context.Context, sigkey *crypto.PrivateKeyK256, recovery string, handle string) (string, map[string]any, error) {
57
pubrotkey, err := c.rotationKey.PublicKey()
58
if err != nil {
59
return "", nil, err
···
61
62
// todo
63
rotationKeys := []string{pubrotkey.DIDKey()}
64
-
if c.recoveryKey != "" {
65
-
rotationKeys = []string{c.recoveryKey, rotationKeys[0]}
66
-
}
67
if recovery != "" {
68
rotationKeys = func(recovery string) []string {
69
newRotationKeys := []string{recovery}
···
74
}(recovery)
75
}
76
77
-
op, err := c.FormatAndSignAtprotoOp(sigkey, handle, rotationKeys, nil)
78
if err != nil {
79
return "", nil, err
80
}
81
82
-
did, err := didForCreateOp(op)
83
if err != nil {
84
return "", nil, err
85
}
86
87
-
return did, op, nil
88
}
89
90
-
func (c *Client) UpdateUserHandle(ctx context.Context, didstr string, nhandle string) error {
91
-
return nil
92
-
}
93
-
94
-
func (c *Client) FormatAndSignAtprotoOp(sigkey *crypto.PrivateKeyK256, handle string, rotationKeys []string, prev *string) (map[string]any, error) {
95
-
pubsigkey, err := sigkey.PublicKey()
96
if err != nil {
97
-
return nil, err
98
}
99
-
100
-
op := map[string]any{
101
-
"type": "plc_operation",
102
-
"verificationMethods": map[string]string{
103
-
"atproto": pubsigkey.DIDKey(),
104
-
},
105
-
"rotationKeys": rotationKeys,
106
-
"alsoKnownAs": []string{"at://" + handle},
107
-
"services": map[string]any{
108
-
"atproto_pds": map[string]string{
109
-
"type": "AtprotoPersonalDataServer",
110
-
"endpoint": "https://" + c.pdsHostname,
111
-
},
112
-
},
113
-
"prev": prev,
114
-
}
115
116
-
b, err := data.MarshalCBOR(op)
117
if err != nil {
118
return nil, err
119
}
···
123
return nil, err
124
}
125
126
-
op["sig"] = base64.RawURLEncoding.EncodeToString(sig)
127
128
-
return op, nil
129
}
130
131
-
func didForCreateOp(op map[string]any) (string, error) {
132
-
b, err := data.MarshalCBOR(op)
133
-
if err != nil {
134
-
return "", err
135
-
}
136
-
137
-
h := sha256.New()
138
-
h.Write(b)
139
-
bs := h.Sum(nil)
140
-
141
-
b32 := strings.ToLower(base32.StdEncoding.EncodeToString(bs))
142
-
143
-
return "did:plc:" + b32[0:24], nil
144
-
}
145
-
146
-
func (c *Client) SendOperation(ctx context.Context, did string, op any) error {
147
b, err := json.Marshal(op)
148
if err != nil {
149
return err
···
162
}
163
defer resp.Body.Close()
164
165
-
fmt.Println(resp.StatusCode)
166
-
167
b, err = io.ReadAll(resp.Body)
168
if err != nil {
169
-
return err
170
}
171
-
172
-
fmt.Println(string(b))
173
174
return nil
175
}
···
14
"strings"
15
16
"github.com/bluesky-social/indigo/atproto/crypto"
17
"github.com/bluesky-social/indigo/util"
18
)
19
20
type Client struct {
21
+
h *http.Client
22
service string
23
+
pdsHostname string
24
rotationKey *crypto.PrivateKeyK256
25
}
26
27
type ClientArgs struct {
28
Service string
29
RotationKey []byte
30
PdsHostname string
31
}
32
···
44
h: util.RobustHTTPClient(),
45
service: args.Service,
46
rotationKey: rk,
47
pdsHostname: args.PdsHostname,
48
}, nil
49
}
50
51
+
func (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
···
61
62
// todo
63
rotationKeys := []string{pubrotkey.DIDKey()}
64
if recovery != "" {
65
rotationKeys = func(recovery string) []string {
66
newRotationKeys := []string{recovery}
···
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
105
+
func 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
115
+
func (c *Client) FormatAndSignAtprotoOp(sigkey *crypto.PrivateKeyK256, op PlcOperation) (*PlcOperation, error) {
116
+
b, err := op.MarshalCBOR()
117
if err != nil {
118
return nil, err
119
}
···
123
return nil, err
124
}
125
126
+
op.Sig = base64.RawURLEncoding.EncodeToString(sig)
127
128
+
return &op, nil
129
}
130
131
+
func (c *Client) SendOperation(ctx context.Context, did string, op *PlcOperation) error {
132
b, err := json.Marshal(op)
133
if err != nil {
134
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
}
+47
plc/types.go
+47
plc/types.go
···
···
1
+
package plc
2
+
3
+
import (
4
+
"encoding/json"
5
+
6
+
"github.com/bluesky-social/indigo/atproto/data"
7
+
cbg "github.com/whyrusleeping/cbor-gen"
8
+
)
9
+
10
+
type PlcOperation struct {
11
+
Type string `json:"type"`
12
+
VerificationMethods map[string]string `json:"verificationMethods"`
13
+
RotationKeys []string `json:"rotationKeys"`
14
+
AlsoKnownAs []string `json:"alsoKnownAs"`
15
+
Services map[string]PlcOperationService `json:"services"`
16
+
Prev *string `json:"prev"`
17
+
Sig string `json:"sig,omitempty"`
18
+
}
19
+
20
+
type PlcOperationService struct {
21
+
Type string `json:"type"`
22
+
Endpoint string `json:"endpoint"`
23
+
}
24
+
25
+
// This is kinda gross. We could just use cborgen i suppose?
26
+
func (po *PlcOperation) MarshalCBOR() ([]byte, error) {
27
+
if po == nil {
28
+
return cbg.CborNull, nil
29
+
}
30
+
31
+
b, err := json.Marshal(po)
32
+
if err != nil {
33
+
return nil, err
34
+
}
35
+
36
+
var m map[string]any
37
+
if err := json.Unmarshal(b, &m); err != nil {
38
+
return nil, err
39
+
}
40
+
41
+
b, err = data.MarshalCBOR(m)
42
+
if err != nil {
43
+
return nil, err
44
+
}
45
+
46
+
return b, nil
47
+
}