···11+// Cryptographic keys and operations as used in atproto
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>
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.
1313+package crypto
···11+package crypto
22+33+import (
44+ "crypto"
55+ "crypto/ecdh"
66+ "crypto/ecdsa"
77+ "crypto/elliptic"
88+ "crypto/rand"
99+ "crypto/sha256"
1010+ "crypto/x509"
1111+ "fmt"
1212+ "math/big"
1313+ "strings"
1414+1515+ "github.com/mr-tron/base58"
1616+ secp256k1 "gitlab.com/yawning/secp256k1-voi"
1717+ secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
1818+)
1919+2020+// Represents the specific support curve type. It is not possible to use [elliptic.Curve] for this because some curves are not in stdlib
2121+type KeyType uint8
2222+2323+const (
2424+ P256 KeyType = 1 // P-256 / secp256r1 / ES256
2525+ K256 KeyType = 2 // K-256 / secp256k1 / ES256K
2626+)
2727+2828+type PrivateKey struct {
2929+ keyType KeyType
3030+ privP256 *ecdsa.PrivateKey
3131+ privK256 *secp256k1secec.PrivateKey
3232+}
3333+3434+type PublicKey struct {
3535+ keyType KeyType
3636+ pubP256 *ecdsa.PublicKey
3737+ pubK256 *secp256k1secec.PublicKey
3838+}
3939+4040+var k256Options = &secp256k1secec.ECDSAOptions{
4141+ // Used to *verify* digest, not to re-hash
4242+ Hash: crypto.SHA256,
4343+ // Use `[R | S]` encoding.
4444+ Encoding: secp256k1secec.EncodingCompact,
4545+ // Checking `s <= n/2` to prevent signature mallability is not part of SEC 1, Version 2.0. libsecp256k1 which used to be used by this package, includes the check, so retain behavior compatibility.
4646+ RejectMalleable: true,
4747+}
4848+4949+// Creates a secure new cryptographic key from scratch, with the indicated curve type.
5050+func GeneratePrivateKey(kt KeyType) (*PrivateKey, error) {
5151+ switch kt {
5252+ case P256:
5353+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
5454+ if err != nil {
5555+ return nil, fmt.Errorf("P-256/secp256r1 key generation failed: %w", err)
5656+ }
5757+ return &PrivateKey{keyType: kt, privP256: key}, nil
5858+ case K256:
5959+ key, err := secp256k1secec.GenerateKey()
6060+ if err != nil {
6161+ return nil, fmt.Errorf("K-256/secp256k1 key generation failed: %w", err)
6262+ }
6363+ return &PrivateKey{keyType: kt, privK256: key}, nil
6464+ default:
6565+ return nil, fmt.Errorf("unexpected crypto KeyType")
6666+ }
6767+}
6868+6969+// Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method.
7070+//
7171+// 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.
7272+func ParsePrivateKeyBytes(data []byte, kt KeyType) (*PrivateKey, error) {
7373+ switch kt {
7474+ case P256:
7575+ // elaborately parse as an ecdh.PrivateKey, then get from that to ecdsa.PrivateKey by encoding/decoding using x509 PKCS8 encoding.
7676+ // Note that the 'data' bytes format is *not* x509 PKCS8!
7777+ skEcdh, err := ecdh.P256().NewPrivateKey(data)
7878+ if err != nil {
7979+ return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err)
8080+ }
8181+ enc, err := x509.MarshalPKCS8PrivateKey(skEcdh)
8282+ if err != nil {
8383+ return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err)
8484+ }
8585+ sk, err := x509.ParsePKCS8PrivateKey(enc)
8686+ if err != nil {
8787+ return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err)
8888+ }
8989+ return &PrivateKey{keyType: kt, privP256: sk.(*ecdsa.PrivateKey)}, nil
9090+ case K256:
9191+ sk, err := secp256k1secec.NewPrivateKey(data)
9292+ if err != nil {
9393+ return nil, fmt.Errorf("invalid K-256/secp256k1 private key: %w", err)
9494+ }
9595+ return &PrivateKey{keyType: kt, privK256: sk}, nil
9696+ default:
9797+ return nil, fmt.Errorf("unexpected crypto KeyType")
9898+ }
9999+}
100100+101101+// Checks if the two private keys are the same. Note that the naive == operator does not work for most equality checks.
102102+func (k *PrivateKey) Equal(other *PrivateKey) bool {
103103+ if k.keyType != other.keyType {
104104+ return false
105105+ }
106106+ switch k.keyType {
107107+ case P256:
108108+ return k.privP256.Equal(other.privP256)
109109+ case K256:
110110+ return k.privK256.Equal(other.privK256)
111111+ default:
112112+ panic("unexpected crypto KeyType")
113113+ }
114114+}
115115+116116+func (k *PrivateKey) KeyType() KeyType {
117117+ return k.keyType
118118+}
119119+120120+// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes].
121121+//
122122+// 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.
123123+func (k *PrivateKey) Bytes() ([]byte, error) {
124124+ switch k.keyType {
125125+ case P256:
126126+ skEcdh, err := k.privP256.ECDH()
127127+ if err != nil {
128128+ return nil, fmt.Errorf("unexpected failure to convert key type: %w", err)
129129+ }
130130+ return skEcdh.Bytes(), nil
131131+ case K256:
132132+ return k.privK256.Bytes(), nil
133133+ default:
134134+ return nil, fmt.Errorf("unexpected crypto KeyType")
135135+ }
136136+}
137137+138138+// Outputs the PublicKey corresponding to this PrivateKey.
139139+func (k *PrivateKey) Public() PublicKey {
140140+ switch k.keyType {
141141+ case P256:
142142+ return PublicKey{
143143+ keyType: k.keyType,
144144+ pubP256: k.privP256.Public().(*ecdsa.PublicKey),
145145+ }
146146+ case K256:
147147+ return PublicKey{
148148+ keyType: k.keyType,
149149+ pubK256: k.privK256.PublicKey(),
150150+ }
151151+ default:
152152+ panic("unexpected crypto KeyType")
153153+ }
154154+}
155155+156156+// First hashes the raw bytes, then signs the digest, returning a binary signature.
157157+//
158158+// 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.
159159+//
160160+// 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.
161161+//
162162+// 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.
163163+func (k *PrivateKey) HashAndSign(content []byte) ([]byte, error) {
164164+ hash := sha256.Sum256(content)
165165+ switch k.keyType {
166166+ case P256:
167167+ r, s, err := ecdsa.Sign(rand.Reader, k.privP256, hash[:])
168168+ if err != nil {
169169+ return nil, fmt.Errorf("crypto error signing with P-256/secp256r1 private key: %w", err)
170170+ }
171171+ s = sigSToLowS_P256(s)
172172+ sig := make([]byte, 64)
173173+ r.FillBytes(sig[:32])
174174+ s.FillBytes(sig[32:])
175175+ return sig, nil
176176+ case K256:
177177+ return k.privK256.Sign(rand.Reader, hash[:], k256Options)
178178+ default:
179179+ return nil, fmt.Errorf("unexpected crypto KeyType")
180180+ }
181181+}
182182+183183+// Checks if the two public keys are the same. Note that the naive == operator does not work for most equality checks.
184184+func (k *PublicKey) Equal(other *PublicKey) bool {
185185+ if k.keyType != other.keyType {
186186+ return false
187187+ }
188188+ switch k.keyType {
189189+ case P256:
190190+ return k.pubP256.Equal(other.pubP256)
191191+ case K256:
192192+ return k.pubK256.Equal(other.pubK256)
193193+ default:
194194+ panic("unexpected crypto KeyType")
195195+ }
196196+}
197197+198198+// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.CompressedBytes] method.
199199+//
200200+// 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.
201201+func ParsePublicCompressedBytes(data []byte, kt KeyType) (*PublicKey, error) {
202202+ switch kt {
203203+ case P256:
204204+ curve := elliptic.P256()
205205+ x, y := elliptic.UnmarshalCompressed(curve, data)
206206+ if x == nil {
207207+ return nil, fmt.Errorf("invalid P-256 public key (x==nil)")
208208+ }
209209+ if !curve.Params().IsOnCurve(x, y) {
210210+ return nil, fmt.Errorf("invalid P-256 public key (not on curve)")
211211+ }
212212+ pub := &ecdsa.PublicKey{
213213+ Curve: curve,
214214+ X: x,
215215+ Y: y,
216216+ }
217217+ return &PublicKey{
218218+ keyType: kt,
219219+ pubP256: pub,
220220+ }, nil
221221+ case K256:
222222+ // secp256k1secec.NewPublicKey accepts any valid encoding, while we
223223+ // explicitly want compressed, so use the explicit point
224224+ // decompression routine.
225225+ p, err := secp256k1.NewIdentityPoint().SetCompressedBytes(data)
226226+ if err != nil {
227227+ return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err)
228228+ }
229229+230230+ pub, err := secp256k1secec.NewPublicKeyFromPoint(p)
231231+ if err != nil {
232232+ return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err)
233233+ }
234234+ return &PublicKey{
235235+ keyType: kt,
236236+ pubK256: pub,
237237+ }, nil
238238+ default:
239239+ return nil, fmt.Errorf("unexpected crypto KeyType")
240240+ }
241241+}
242242+243243+// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.CompressedBytes] method.
244244+//
245245+// 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.
246246+func ParsePublicUncompressedBytes(data []byte, kt KeyType) (*PublicKey, error) {
247247+ switch kt {
248248+ case P256:
249249+ curve := elliptic.P256()
250250+ x, y := elliptic.Unmarshal(curve, data)
251251+ if x == nil {
252252+ return nil, fmt.Errorf("invalid P-256 public key (x==nil)")
253253+ }
254254+ if !curve.Params().IsOnCurve(x, y) {
255255+ return nil, fmt.Errorf("invalid P-256 public key (not on curve)")
256256+ }
257257+ pub := &ecdsa.PublicKey{
258258+ Curve: curve,
259259+ X: x,
260260+ Y: y,
261261+ }
262262+ return &PublicKey{
263263+ keyType: kt,
264264+ pubP256: pub,
265265+ }, nil
266266+ case K256:
267267+ pub, err := secp256k1secec.NewPublicKey(data)
268268+ if err != nil {
269269+ return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err)
270270+ }
271271+ return &PublicKey{
272272+ keyType: kt,
273273+ pubK256: pub,
274274+ }, nil
275275+ default:
276276+ return nil, fmt.Errorf("unexpected crypto KeyType")
277277+ }
278278+}
279279+280280+// Parses a public key in multibase encoding, as would be found in a DID Document `verificationMethod` section.
281281+//
282282+// This implementation does not handle the many possible multibase encodings (eg, base32), only the base58btc encoding that would be found in a DID Document.
283283+func ParsePublicMultibase(encoded string, kt KeyType) (*PublicKey, error) {
284284+ if len(encoded) < 2 || encoded[0] != 'z' {
285285+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
286286+ }
287287+ data, err := base58.Decode(encoded[1:])
288288+ if err != nil {
289289+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
290290+ }
291291+ return ParsePublicUncompressedBytes(data, kt)
292292+}
293293+294294+// Parses a public key in a variant of multibase encoding, with no key type indicator (unlike did:key), but with key compression (unlike `verificationMethod` in a DID Document).
295295+func ParsePublicCompressedMultibase(encoded string, kt KeyType) (*PublicKey, error) {
296296+ if len(encoded) < 2 || encoded[0] != 'z' {
297297+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
298298+ }
299299+ data, err := base58.Decode(encoded[1:])
300300+ if err != nil {
301301+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
302302+ }
303303+ return ParsePublicCompressedBytes(data, kt)
304304+}
305305+306306+// Loads a [PublicKey] from did:key string serialization.
307307+//
308308+// The did:key format encodes the key type.
309309+func ParsePublicDidKey(didKey string) (*PublicKey, error) {
310310+ if !strings.HasPrefix(didKey, "did:key:z") {
311311+ return nil, fmt.Errorf("string is not a DID key: %s", didKey)
312312+ }
313313+ mb := strings.TrimPrefix(didKey, "did:key:z")
314314+ data, err := base58.Decode(mb)
315315+ if err != nil || len(data) < 2 {
316316+ return nil, fmt.Errorf("crypto: not a multibase base58btc string")
317317+ }
318318+ if data[0] == 0x80 && data[1] == 0x24 {
319319+ // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24]
320320+ return ParsePublicCompressedBytes(data[2:], P256)
321321+ } else if data[0] == 0xE7 && data[1] == 0x01 {
322322+ // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01]
323323+ return ParsePublicCompressedBytes(data[2:], K256)
324324+ } else {
325325+ return nil, fmt.Errorf("unexpected did:key multicode value")
326326+ }
327327+}
328328+329329+// Serializes the [PublicKey] in to "uncompressed" binary format.
330330+func (k *PublicKey) UncompressedBytes() []byte {
331331+ switch k.keyType {
332332+ case P256:
333333+ pkEcdh, err := k.pubP256.ECDH()
334334+ if err != nil {
335335+ panic("unexpected invalid P-256/secp256r1 public key (internal)")
336336+ }
337337+ return pkEcdh.Bytes()
338338+ case K256:
339339+ p := k.pubK256.Point()
340340+ // NOTE: is this check necessary for uncompressed bytes? came from go-did
341341+ if p.IsIdentity() != 0 {
342342+ panic("unexpected invalid K-256/secp256k1 public key (internal)")
343343+ }
344344+ return p.UncompressedBytes()
345345+ default:
346346+ panic("unexpected crypto KeyType")
347347+ }
348348+}
349349+350350+// Serializes the [PublicKey] in to "compressed" binary format.
351351+func (k *PublicKey) CompressedBytes() []byte {
352352+ switch k.keyType {
353353+ case P256:
354354+ if !k.pubP256.Curve.IsOnCurve(k.pubP256.X, k.pubP256.Y) {
355355+ panic("unexpected invalid P-256/secp256r1 public key (internal)")
356356+ }
357357+ return elliptic.MarshalCompressed(k.pubP256.Curve, k.pubP256.X, k.pubP256.Y)
358358+ case K256:
359359+ p := k.pubK256.Point()
360360+ if p.IsIdentity() != 0 {
361361+ panic("unexpected invalid K-256/secp256k1 public key (internal)")
362362+ }
363363+ return p.CompressedBytes()
364364+ default:
365365+ panic("unexpected crypto KeyType")
366366+ }
367367+}
368368+369369+// First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure.
370370+//
371371+// 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.
372372+//
373373+// Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function.
374374+//
375375+// This method requires a "low-S" signature, as specified by atproto.
376376+func (k *PublicKey) HashAndVerify(content, sig []byte) error {
377377+ hash := sha256.Sum256(content)
378378+ switch k.keyType {
379379+ case P256:
380380+ // parseP256Sig
381381+ if len(sig) != 64 {
382382+ return fmt.Errorf("crypto: P-256 signatures must be 64 bytes, got len=%d", len(sig))
383383+ }
384384+ r := big.NewInt(0)
385385+ s := big.NewInt(0)
386386+ r.SetBytes(sig[:32])
387387+ s.SetBytes(sig[32:])
388388+389389+ if !ecdsa.Verify(k.pubP256, hash[:], r, s) {
390390+ return fmt.Errorf("crypto: invalid signature")
391391+ }
392392+393393+ // ensure that signature is low-S
394394+ if !sigSIsLowS_P256(s) {
395395+ return fmt.Errorf("crypto: invalid signature (high-S P-256)")
396396+ }
397397+398398+ return nil
399399+ case K256:
400400+ if !k.pubK256.Verify(hash[:], sig, k256Options) {
401401+ return fmt.Errorf("crypto: invalid signature")
402402+ }
403403+ return nil
404404+ default:
405405+ return fmt.Errorf("unexpected crypto KeyType")
406406+ }
407407+}
408408+409409+// Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation:
410410+//
411411+// - compressed / compacted binary representation
412412+// - prefix with appropriate curve multicodec bytes
413413+// - encode bytes with base58btc
414414+// - add "z" prefix to indicate encoding
415415+// - add "did:key:" prefix
416416+func (k *PublicKey) DidKey() string {
417417+ kbytes := k.CompressedBytes()
418418+ switch k.keyType {
419419+ case P256:
420420+ // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24]
421421+ kbytes = append([]byte{0x80, 0x24}, kbytes...)
422422+ case K256:
423423+ // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01]
424424+ kbytes = append([]byte{0xE7, 0x01}, kbytes...)
425425+ default:
426426+ panic("unexpected crypto KeyType")
427427+ }
428428+ return "did:key:z" + base58.Encode(kbytes)
429429+}
430430+431431+// Returns multibase string encoding of the public key, as would be included in a DID Document "verificationMethod" section:
432432+//
433433+// - non-compressed / non-compacted binary representation
434434+// - encode bytes with base58btc
435435+// - prefix "z" (lower-case) to indicate encoding
436436+func (k *PublicKey) Multibase() string {
437437+ kbytes := k.UncompressedBytes()
438438+ return "z" + base58.Encode(kbytes)
439439+}
440440+441441+// Variant of Multibase() which outputs compressed key format.
442442+func (k *PublicKey) CompressedMultibase() string {
443443+ kbytes := k.CompressedBytes()
444444+ return "z" + base58.Encode(kbytes)
445445+}
446446+447447+func (k *PublicKey) KeyType() KeyType {
448448+ return k.keyType
449449+}
450450+451451+// Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`.
452452+func (k *PublicKey) DidDocSuite() string {
453453+ switch k.keyType {
454454+ case P256:
455455+ return "EcdsaSecp256r1VerificationKey2019"
456456+ case K256:
457457+ // NOTE: this is not a W3C standard suite, and will probably be replaced with "Multikey"
458458+ return "EcdsaSecp256k1VerificationKey2019"
459459+ default:
460460+ panic("unexpected crypto KeyType")
461461+ }
462462+}
···11+package crypto
22+33+import (
44+ "crypto/elliptic"
55+ "math/big"
66+)
77+88+var curveN_P256 *big.Int = elliptic.P256().Params().N
99+var curveHalfOrder_P256 *big.Int = new(big.Int).Rsh(curveN_P256, 1)
1010+1111+// Checks if 'S' value from a P-256 signature is "low-S".
1212+// un-reviewed, un-safe code from: https://github.com/golang/go/issues/54549
1313+func sigSIsLowS_P256(s *big.Int) bool {
1414+ if s.Cmp(curveHalfOrder_P256) == 1 {
1515+ return false
1616+ }
1717+ return true
1818+}
1919+2020+// Ensures that 'S' value from a P-256 signature is "low-S" variant.
2121+// un-reviewed, un-safe code from: https://github.com/golang/go/issues/54549
2222+func sigSToLowS_P256(s *big.Int) *big.Int {
2323+2424+ if !sigSIsLowS_P256(s) {
2525+ // Set s to N - s that will be then in the lower part of signature space
2626+ // less or equal to half order
2727+ s.Sub(curveN_P256, s)
2828+ return s
2929+ }
3030+ return s
3131+}