···11-// Cryptographic keys and operations as used in atproto
11+// Package crypto provides cryptographic keys and operations, as used in atproto (the protocol)
22//
33// This package attempts to abstract away the specific curves, compressions, signature variations, and other implementation details. The goal is to provide as few knobs and options as possible when working with this library. Use of cryptography in atproto is specified in https://atproto.com/specs/cryptography.
44//
55// The two currently supported curve types are:
66//
77// - P-256/secp256r1, internally implemented using golang's stdlib cryptographic library
88-// - K-256/secp256r1, internally implemented using <gitlab.com/yawning/secp256k1-voi>
88+// - K-256/secp256r1, internally implemented using https://gitlab.com/yawning/secp256k1-voi
99//
1010// "Low-S" signatures are enforced for both key types, both when creating signatures and during verification, as required by the atproto specification.
1111-//
1212-// This package uses concrete types for private keys, meaning that the secret key material is present in memory.
1311package crypto
+14-11
atproto/crypto/k256.go
···1111 secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
1212)
13131414-// K-256 / secp256k1 / ES256K
1414+// Implements the [PrivateKeyExportable] and [PrivateKey] interfaces for the NIST K-256 / secp256k1 / ES256K cryptographic curve.
1515+// Secret key material is naively stored in memory.
1516type PrivateKeyK256 struct {
1617 privK256 *secp256k1secec.PrivateKey
1718}
18192020+// K-256 / secp256k1 / ES256K
2121+// Implements the [PublicKey] interface for the NIST K-256 / secp256k1 / ES256K cryptographic curve.
1922type PublicKeyK256 struct {
2023 pubK256 *secp256k1secec.PublicKey
2124}
···3841 return &PrivateKeyK256{privK256: key}, nil
3942}
40434141-// Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method. (XXX)
4444+// Loads a [PrivateKeyK256] from raw bytes, as exported by the PrivateKey.Bytes method.
4245//
4346// Calling code needs to know the key type ahead of time, and must remove any string encoding (hex encoding, base64, etc) before calling this function.
4447func ParsePrivateBytesK256(data []byte) (*PrivateKeyK256, error) {
···5053}
51545255// Checks if the two private keys are the same. Note that the naive == operator does not work for most equality checks.
5353-func (k *PrivateKeyK256) Equal(other PrivateKeyExportable) bool {
5656+func (k *PrivateKeyK256) Equal(other PrivateKey) bool {
5457 otherK256, ok := other.(*PrivateKeyK256)
5558 if ok {
5659 return k.privK256.Equal(otherK256.privK256)
···5861 return false
5962}
60636161-// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes].
6464+// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateBytesK256].
6265//
6363-// The encoding format is curve-specific, and is generally "compact" for private keys. Both P-256 and K-256 private keys end up 32 bytes long. There is no ASN.1 or other enclosing structure to the binary encoding.
6666+// For K-256, this is the "compact" encoding and is 32 bytes long. There is no ASN.1 or other enclosing structure.
6467func (k *PrivateKeyK256) Bytes() []byte {
6568 return k.privK256.Bytes()
6669}
67706868-// Outputs the PublicKey corresponding to this PrivateKey.
7171+// Outputs the [PublicKey] corresponding to this [PrivateKeyK256]; it will be a [PublicKeyK256].
6972func (k *PrivateKeyK256) Public() (PublicKey, error) {
7073 pub := PublicKeyK256{pubK256: k.privK256.PublicKey()}
7174 err := pub.ensureBytes()
···7982//
8083// SHA-256 is the hash algorithm used, as specified by atproto. Signing digests is the norm for ECDSA, and required by some backend implementations. This method does not "double hash", it simply has name which clarifies that hashing is happening.
8184//
8282-// Calling code is responsible for any string encoding of signatures (eg, hex or base64). Both P-256 and K-256 signatures are 64 bytes long.
8585+// Calling code is responsible for any string encoding of signatures (eg, hex or base64). For K-256, the signature is 64 bytes long.
8386//
8487// NIST ECDSA signatures can have a "malleability" issue, meaning that there are multiple valid signatures for the same content with the same signing key. This method always returns a "low-S" signature, as required by atproto.
8588func (k *PrivateKeyK256) HashAndSign(content []byte) ([]byte, error) {
···8790 return k.privK256.Sign(rand.Reader, hash[:], k256Options)
8891}
89929090-// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format.
9393+// Loads a [PublicKeyK256] raw bytes, as exported by the PublicKey.Bytes method. This is the "compressed" curve format.
9194//
9295// Calling code needs to know the key type ahead of time, and must remove any string encoding (hex encoding, base64, etc) before calling this function.
9396func ParsePublicBytesK256(data []byte) (*PublicKeyK256, error) {
···111114 return &pub, nil
112115}
113116114114-// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method.
117117+// Loads a [PublicKeyK256] from raw bytes, as exported by the PublicKey.UncompressedBytes method.
115118//
116119// Calling code needs to know the key type ahead of time, and must remove any string encoding (hex encoding, base64, etc) before calling this function.
117120func ParsePublicUncompressedBytesK256(data []byte) (*PublicKeyK256, error) {
···145148 return nil
146149}
147150148148-// Serializes the [PublicKey] in to "uncompressed" binary format.
151151+// Serializes the key in to "uncompressed" binary format.
149152func (k *PublicKeyK256) UncompressedBytes() []byte {
150153 p := k.pubK256.Point()
151154 return p.UncompressedBytes()
152155}
153156154154-// Serializes the [PublicKey] in to "compressed" binary format.
157157+// Serializes the key in to "compressed" binary format.
155158func (k *PublicKeyK256) Bytes() []byte {
156159 p := k.pubK256.Point()
157160 return p.CompressedBytes()
+71-28
atproto/crypto/keys.go
···77 "github.com/mr-tron/base58"
88)
991010+// Common interface for private keys of all the supported cryptographic systems in the atproto ecosystem, in a format which may or may not have secret key material directly available in memory to be exported as bytes.
1011type PrivateKey interface {
1212+ Equal(other PrivateKey) bool
1313+1414+ // Returns the public key for this private key. Verifies that the public
1515+ // key is valid and will be possible to encode as bytes or a string later.
1116 Public() (PublicKey, error)
1717+1818+ // First hashes the raw bytes, then signs the digest, returning a binary
1919+ // signature. SHA-256 is the hash algorithm used, as specified by atproto.
2020+ // This method always returns a "low-S" signature, as required by atproto.
1221 HashAndSign(content []byte) ([]byte, error)
1322}
14232424+// Common interface for private keys of all the supported cryptographic systems in the atproto ecosystem, in a format which does have secret key material directly available in memory to be exported as bytes.
1525type PrivateKeyExportable interface {
2626+ Equal(other PrivateKey) bool
2727+2828+ // Outputs an untyped (no multicodec) compact encoding of the secret key
2929+ // material. The encoding format is curve-specific, and is generally
3030+ // "compact" for private keys. Both P-256 and K-256 private keys end up 32
3131+ // bytes long. There is no ASN.1 or other enclosing structure to the binary
3232+ // encoding.
1633 Bytes() []byte
1717- Equal(other PrivateKeyExportable) bool
3434+3535+ // Same as PrivateKey.Public()
1836 Public() (PublicKey, error)
3737+3838+ // Same as PrivateKey.HashAndSign()
1939 HashAndSign(content []byte) ([]byte, error)
2040}
21414242+// Common interface for public keys of all the supported cryptographic systems in the atproto ecosystem.
2243type PublicKey interface {
2323- UncompressedBytes() []byte
2424- Bytes() []byte
2544 Equal(other PublicKey) bool
4545+4646+ // Outputs a compact byte serialization of this key.
4747+ Bytes() []byte
4848+4949+ // Hashes content bytes with SHA-256, then verifies the signature of the
5050+ // digest.
2651 HashAndVerify(content, sig []byte) error
5252+5353+ // String serialization of the public key using common parameters:
5454+ // compressed byte serialization; multicode varint code prefix; base58btc
5555+ // string encoding ("z" prefix)
2756 Multibase() string
5757+5858+ // String serialization of the public key as did:key.
2859 DidKey() string
29603030- // these are likely to be deprecated
6161+ // Outputs a non-compact byte serialization of this key. This is not used
6262+ // frequently, or directly in atproto, but some serializations and
6363+ // encodings require it.
6464+ // For curves with no compressed/uncompressed distinction, returns the same
6565+ // value as Bytes().
6666+ UncompressedBytes() []byte
6767+6868+ // Outputs a DID cryptographic suite type name for this curve, as used in
6969+ // some DID documents.
7070+ // This method may be removed in the future.
3171 LegacyDidDocSuite() string
7272+7373+ // Helper to serialize in a format used in older DID documents:
7474+ // uncompressed byte encoding, no multicodec prefix, base58btc multibase
7575+ // string encoding ("z" prefix)
7676+ // This method may be removed in the future.
3277 LegacyMultibase() string
3378}
34793535-// Parses a public key in multibase encoding, as would be found in a older DID Document `verificationMethod` section.
3636-//
3737-// This implementation does not handle the many possible multibase encodings (eg, base32), only the base58btc encoding that would be found in a DID Document.
3838-//
3939-// This function is deprecated!
4040-func ParsePublicLegacyMultibase(encoded string, didDocSuite string) (PublicKey, error) {
4141- if len(encoded) < 2 || encoded[0] != 'z' {
4242- return nil, fmt.Errorf("crypto: not a multibase base58btc string")
4343- }
4444- data, err := base58.Decode(encoded[1:])
4545- if err != nil {
4646- return nil, fmt.Errorf("crypto: not a multibase base58btc string")
4747- }
4848- switch didDocSuite {
4949- case "EcdsaSecp256r1VerificationKey2019":
5050- return ParsePublicUncompressedBytesP256(data)
5151- case "EcdsaSecp256k1VerificationKey2019":
5252- return ParsePublicUncompressedBytesK256(data)
5353- default:
5454- return nil, fmt.Errorf("unhandled legacy crypto suite: %s", didDocSuite)
5555- }
5656-}
5757-5880// Parses a public key from multibase encoding, with multicodec indicating the key type.
5981func ParsePublicMultibase(encoded string) (PublicKey, error) {
6082 if len(encoded) < 2 || encoded[0] != 'z' {
···7496 // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01]
7597 return ParsePublicBytesK256(data[2:])
7698 } else {
7777- return nil, fmt.Errorf("unexpected multicode code for multibase-encoded key")
9999+ return nil, fmt.Errorf("unsupported atproto key type (unknown multicodec prefix)")
78100 }
79101}
80102···88110 mb := strings.TrimPrefix(didKey, "did:key:")
89111 return ParsePublicMultibase(mb)
90112}
113113+114114+// Parses a public key in multibase encoding, as would be found in a older DID Document `verificationMethod` section: uncompressed binary, no multicodec prefix, and base58btc multibase string encoding.
115115+//
116116+// This function is likely to be deprecated and removed.
117117+func ParsePublicLegacyMultibase(encoded string, didDocSuite string) (PublicKey, error) {
118118+ if len(encoded) < 2 || encoded[0] != 'z' {
119119+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
120120+ }
121121+ data, err := base58.Decode(encoded[1:])
122122+ if err != nil {
123123+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
124124+ }
125125+ switch didDocSuite {
126126+ case "EcdsaSecp256r1VerificationKey2019":
127127+ return ParsePublicUncompressedBytesP256(data)
128128+ case "EcdsaSecp256k1VerificationKey2019":
129129+ return ParsePublicUncompressedBytesK256(data)
130130+ default:
131131+ return nil, fmt.Errorf("unhandled legacy crypto suite: %s", didDocSuite)
132132+ }
133133+}
+24-22
atproto/crypto/p256.go
···1313 "github.com/mr-tron/base58"
1414)
15151616-// P-256 / secp256r1 / ES256
1616+// Implements the [PrivateKeyExportable] and [PrivateKey] interfaces for the NIST P-256 / secp256r1 / ES256 cryptographic curve.
1717+// Secret key material is naively stored in memory.
1718type PrivateKeyP256 struct {
1819 privP256 *ecdsa.PrivateKey
1920}
20212222+// Implements the [PublicKey] interface for the NIST P-256 / secp256r1 / ES256 cryptographic curve.
2123type PublicKeyP256 struct {
2224 pubP256 *ecdsa.PublicKey
2325}
24262525-// Creates a secure new cryptographic key from scratch, with the indicated curve type.
2727+// Creates a secure new cryptographic key from scratch.
2628func GeneratePrivateKeyP256() (*PrivateKeyP256, error) {
2729 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
2830 if err != nil {
···3638 return &priv, nil
3739}
38403939-// Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method.
4141+// Loads a [PrivateKeyP256] from raw bytes, as exported by the PrivateKeyP256.Bytes method.
4042//
4143// Calling code needs to know the key type ahead of time, and must remove any string encoding (hex encoding, base64, etc) before calling this function.
4244func ParsePrivateBytesP256(data []byte) (*PrivateKeyP256, error) {
···6365}
64666567// Checks if the two private keys are the same. Note that the naive == operator does not work for most equality checks.
6666-func (k *PrivateKeyP256) Equal(other PrivateKeyExportable) bool {
6868+func (k *PrivateKeyP256) Equal(other PrivateKey) bool {
6769 otherP256, ok := other.(*PrivateKeyP256)
6870 if ok {
6971 return k.privP256.Equal(otherP256.privP256)
···7173 return false
7274}
73757474-// Outputs the PublicKey corresponding to this PrivateKey.
7575-func (k *PrivateKeyP256) Public() (PublicKey, error) {
7676- pub := PublicKeyP256{pubP256: k.privP256.Public().(*ecdsa.PublicKey)}
7777- err := pub.ensureBytes()
7878- if err != nil {
7979- return nil, err
8080- }
8181- return &pub, nil
8282-}
8383-8476// internal helper which checks that they key will be possible to export later
8577func (k *PrivateKeyP256) ensureBytes() error {
8678 _, err := k.privP256.ECDH()
8779 return err
8880}
89819090-// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes].
8282+// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateBytesP256].
9183//
9292-// The encoding format is curve-specific, and is generally "compact" for private keys. Both P-256 and K-256 private keys end up 32 bytes long. There is no ASN.1 or other enclosing structure to the binary encoding.
8484+// For P-256, this is the "compact" encoding and is 32 bytes long. There is no ASN.1 or other enclosing structure.
9385func (k *PrivateKeyP256) Bytes() []byte {
9486 skEcdh, err := k.privP256.ECDH()
9587 if err != nil {
···9890 return skEcdh.Bytes()
9991}
100929393+// Outputs the [PublicKey] corresponding to this [PrivateKeyP256]; it will be a [PublicKeyP256].
9494+func (k *PrivateKeyP256) Public() (PublicKey, error) {
9595+ pub := PublicKeyP256{pubP256: k.privP256.Public().(*ecdsa.PublicKey)}
9696+ err := pub.ensureBytes()
9797+ if err != nil {
9898+ return nil, err
9999+ }
100100+ return &pub, nil
101101+}
102102+101103// First hashes the raw bytes, then signs the digest, returning a binary signature.
102104//
103105// SHA-256 is the hash algorithm used, as specified by atproto. Signing digests is the norm for ECDSA, and required by some backend implementations. This method does not "double hash", it simply has name which clarifies that hashing is happening.
104106//
105105-// Calling code is responsible for any string encoding of signatures (eg, hex or base64). Both P-256 and K-256 signatures are 64 bytes long.
107107+// Calling code is responsible for any string encoding of signatures (eg, hex or base64). For P-256, the signature is 64 bytes long.
106108//
107109// NIST ECDSA signatures can have a "malleability" issue, meaning that there are multiple valid signatures for the same content with the same signing key. This method always returns a "low-S" signature, as required by atproto.
108110func (k *PrivateKeyP256) HashAndSign(content []byte) ([]byte, error) {
···118120 return sig, nil
119121}
120122121121-// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format.
123123+// Loads a [PublicKeyP256] raw bytes, as exported by the PublicKey.Bytes method. This is the "compressed" curve format.
122124//
123125// Calling code needs to know the key type ahead of time, and must remove any string encoding (hex encoding, base64, etc) before calling this function.
124126func ParsePublicBytesP256(data []byte) (*PublicKeyP256, error) {
···143145 return &pub, nil
144146}
145147146146-// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method.
148148+// Loads a [PublicKeyP256] from raw bytes, as exported by the PublicKey.UncompressedBytes method.
147149//
148150// Calling code needs to know the key type ahead of time, and must remove any string encoding (hex encoding, base64, etc) before calling this function.
149151func ParsePublicUncompressedBytesP256(data []byte) (*PublicKeyP256, error) {
···186188 return err
187189}
188190189189-// Serializes the [PublicKey] in to "uncompressed" binary format.
191191+// Serializes the key in to "uncompressed" binary format.
190192func (k *PublicKeyP256) UncompressedBytes() []byte {
191193 pkEcdh, err := k.pubP256.ECDH()
192194 if err != nil {
···195197 return pkEcdh.Bytes()
196198}
197199198198-// Serializes the [PublicKey] in to "compressed" binary format.
200200+// Serializes the key in to "compressed" binary format.
199201func (k *PublicKeyP256) Bytes() []byte {
200202 return elliptic.MarshalCompressed(k.pubP256.Curve, k.pubP256.X, k.pubP256.Y)
201203}
···230232 return nil
231233}
232234233233-// Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization
235235+// Returns a multibase string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization
234236func (k *PublicKeyP256) Multibase() string {
235237 kbytes := k.Bytes()
236238 // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24]