1package repo
2
3import (
4 "bytes"
5 "fmt"
6
7 "github.com/bluesky-social/indigo/atproto/crypto"
8 "github.com/bluesky-social/indigo/atproto/data"
9 "github.com/bluesky-social/indigo/atproto/syntax"
10
11 "github.com/ipfs/go-cid"
12)
13
14// atproto repo commit object as a struct type. Can be used for direct CBOR or JSON serialization.
15type Commit struct {
16 DID string `json:"did" cborgen:"did"`
17 Version int64 `json:"version" cborgen:"version"` // currently: 3
18 Prev *cid.Cid `json:"prev" cborgen:"prev"` // NOTE: omitempty would break signature verification for repo v3
19 Data cid.Cid `json:"data" cborgen:"data"`
20 Sig []byte `json:"sig,omitempty" cborgen:"sig,omitempty"`
21 Rev string `json:"rev,omitempty" cborgen:"rev,omitempty"`
22}
23
24// does basic checks that field values and syntax are correct
25func (c *Commit) VerifyStructure() error {
26 if c.Version != ATPROTO_REPO_VERSION {
27 return fmt.Errorf("unsupported repo version: %d", c.Version)
28 }
29 if len(c.Sig) == 0 {
30 return fmt.Errorf("empty commit signature")
31 }
32 _, err := syntax.ParseDID(c.DID)
33 if err != nil {
34 return fmt.Errorf("invalid commit data: %w", err)
35 }
36 _, err = syntax.ParseTID(c.Rev)
37 if err != nil {
38 return fmt.Errorf("invalid commit data: %w", err)
39 }
40 return nil
41}
42
43// returns a representation of the commit object as atproto data (eg, for JSON serialization)
44func (c *Commit) AsData() map[string]any {
45 d := map[string]any{
46 "did": c.DID,
47 "version": c.Version,
48 "prev": (*data.CIDLink)(c.Prev),
49 "data": data.CIDLink(c.Data),
50 }
51 if c.Sig != nil {
52 d["sig"] = data.Bytes(c.Sig)
53 }
54 if c.Rev != "" {
55 d["rev"] = c.Rev
56 }
57 return d
58}
59
60// Encodes the commit object as DAG-CBOR, without the signature field. Used for signing or validating signatures.
61func (c *Commit) UnsignedBytes() ([]byte, error) {
62 buf := new(bytes.Buffer)
63 if c.Sig == nil {
64 if err := c.MarshalCBOR(buf); err != nil {
65 return nil, err
66 }
67 return buf.Bytes(), nil
68 }
69 unsigned := Commit{
70 DID: c.DID,
71 Version: c.Version,
72 Prev: c.Prev,
73 Data: c.Data,
74 Rev: c.Rev,
75 }
76 if err := unsigned.MarshalCBOR(buf); err != nil {
77 return nil, err
78 }
79 return buf.Bytes(), nil
80}
81
82// Signs the commit, storing the signature in the `Sig` field
83func (c *Commit) Sign(privkey crypto.PrivateKey) error {
84 b, err := c.UnsignedBytes()
85 if err != nil {
86 return err
87 }
88 sig, err := privkey.HashAndSign(b)
89 if err != nil {
90 return err
91 }
92 c.Sig = sig
93 return nil
94}
95
96// Verifies `Sig` field using the provided key. Returns `nil` if signature is valid.
97func (c *Commit) VerifySignature(pubkey crypto.PublicKey) error {
98 if c.Sig == nil {
99 return fmt.Errorf("can not verify unsigned commit")
100 }
101 b, err := c.UnsignedBytes()
102 if err != nil {
103 return err
104 }
105 return pubkey.HashAndVerify(b, c.Sig)
106}