···11+package crypto
22+33+import (
44+ "crypto"
55+ "crypto/rand"
66+ "crypto/sha256"
77+ "fmt"
88+99+ "github.com/mr-tron/base58"
1010+ secp256k1 "gitlab.com/yawning/secp256k1-voi"
1111+ secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
1212+)
1313+1414+// K-256 / secp256k1 / ES256K
1515+type PrivateKeyK256 struct {
1616+ privK256 *secp256k1secec.PrivateKey
1717+}
1818+1919+type PublicKeyK256 struct {
2020+ pubK256 *secp256k1secec.PublicKey
2121+}
2222+2323+var k256Options = &secp256k1secec.ECDSAOptions{
2424+ // Used to *verify* digest, not to re-hash
2525+ Hash: crypto.SHA256,
2626+ // Use `[R | S]` encoding.
2727+ Encoding: secp256k1secec.EncodingCompact,
2828+ // 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.
2929+ RejectMalleable: true,
3030+}
3131+3232+// Creates a secure new cryptographic key from scratch, with the indicated curve type.
3333+func GeneratePrivateKeyK256() (*PrivateKeyK256, error) {
3434+ key, err := secp256k1secec.GenerateKey()
3535+ if err != nil {
3636+ return nil, fmt.Errorf("K-256/secp256k1 key generation failed: %w", err)
3737+ }
3838+ return &PrivateKeyK256{privK256: key}, nil
3939+}
4040+4141+// Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method. (XXX)
4242+//
4343+// 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.
4444+func ParsePrivateBytesK256(data []byte) (*PrivateKeyK256, error) {
4545+ sk, err := secp256k1secec.NewPrivateKey(data)
4646+ if err != nil {
4747+ return nil, fmt.Errorf("invalid K-256/secp256k1 private key: %w", err)
4848+ }
4949+ return &PrivateKeyK256{privK256: sk}, nil
5050+}
5151+5252+// 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 {
5454+ otherK256, ok := other.(*PrivateKeyK256)
5555+ if ok {
5656+ return k.privK256.Equal(otherK256.privK256)
5757+ }
5858+ return false
5959+}
6060+6161+// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes].
6262+//
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.
6464+func (k *PrivateKeyK256) Bytes() []byte {
6565+ return k.privK256.Bytes()
6666+}
6767+6868+// Outputs the PublicKey corresponding to this PrivateKey.
6969+func (k *PrivateKeyK256) Public() (PublicKey, error) {
7070+ pub := PublicKeyK256{pubK256: k.privK256.PublicKey()}
7171+ err := pub.ensureBytes()
7272+ if err != nil {
7373+ return nil, err
7474+ }
7575+ return &pub, nil
7676+}
7777+7878+// First hashes the raw bytes, then signs the digest, returning a binary signature.
7979+//
8080+// 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.
8181+//
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.
8383+//
8484+// 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.
8585+func (k *PrivateKeyK256) HashAndSign(content []byte) ([]byte, error) {
8686+ hash := sha256.Sum256(content)
8787+ return k.privK256.Sign(rand.Reader, hash[:], k256Options)
8888+}
8989+9090+// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format.
9191+//
9292+// 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.
9393+func ParsePublicBytesK256(data []byte) (*PublicKeyK256, error) {
9494+ // secp256k1secec.NewPublicKey accepts any valid encoding, while we
9595+ // explicitly want compressed, so use the explicit point
9696+ // decompression routine.
9797+ p, err := secp256k1.NewIdentityPoint().SetCompressedBytes(data)
9898+ if err != nil {
9999+ return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err)
100100+ }
101101+102102+ pubK, err := secp256k1secec.NewPublicKeyFromPoint(p)
103103+ if err != nil {
104104+ return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err)
105105+ }
106106+ pub := PublicKeyK256{pubK256: pubK}
107107+ err = pub.ensureBytes()
108108+ if err != nil {
109109+ return nil, err
110110+ }
111111+ return &pub, nil
112112+}
113113+114114+// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method.
115115+//
116116+// 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.
117117+func ParsePublicUncompressedBytesK256(data []byte) (*PublicKeyK256, error) {
118118+ pubK, err := secp256k1secec.NewPublicKey(data)
119119+ if err != nil {
120120+ return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err)
121121+ }
122122+ pub := PublicKeyK256{pubK256: pubK}
123123+ err = pub.ensureBytes()
124124+ if err != nil {
125125+ return nil, err
126126+ }
127127+ return &pub, nil
128128+}
129129+130130+// Checks if the two public keys are the same. Note that the naive == operator does not work for most equality checks.
131131+func (k *PublicKeyK256) Equal(other PublicKey) bool {
132132+ otherK256, ok := other.(*PublicKeyK256)
133133+ if ok {
134134+ return k.pubK256.Equal(otherK256.pubK256)
135135+ }
136136+ return false
137137+}
138138+139139+// verifies that this public key is safe to export as bytes later on
140140+func (k *PublicKeyK256) ensureBytes() error {
141141+ p := k.pubK256.Point()
142142+ if p.IsIdentity() != 0 {
143143+ return fmt.Errorf("unexpected invalid K-256/secp256k1 public key (internal)")
144144+ }
145145+ return nil
146146+}
147147+148148+// Serializes the [PublicKey] in to "uncompressed" binary format.
149149+func (k *PublicKeyK256) UncompressedBytes() []byte {
150150+ p := k.pubK256.Point()
151151+ return p.UncompressedBytes()
152152+}
153153+154154+// Serializes the [PublicKey] in to "compressed" binary format.
155155+func (k *PublicKeyK256) Bytes() []byte {
156156+ p := k.pubK256.Point()
157157+ return p.CompressedBytes()
158158+}
159159+160160+// First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure.
161161+//
162162+// 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.
163163+//
164164+// Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function.
165165+//
166166+// This method requires a "low-S" signature, as specified by atproto.
167167+func (k *PublicKeyK256) HashAndVerify(content, sig []byte) error {
168168+ hash := sha256.Sum256(content)
169169+ if !k.pubK256.Verify(hash[:], sig, k256Options) {
170170+ return fmt.Errorf("crypto: invalid signature")
171171+ }
172172+ return nil
173173+}
174174+175175+// Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization
176176+func (k *PublicKeyK256) Multibase() string {
177177+ kbytes := k.Bytes()
178178+ // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01]
179179+ kbytes = append([]byte{0xE7, 0x01}, kbytes...)
180180+ return "z" + base58.Encode(kbytes)
181181+}
182182+183183+// Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`.
184184+func (k *PublicKeyK256) LegacyDidDocSuite() string {
185185+ // NOTE: this is not a W3C standard suite, and will probably be replaced with "Multikey"
186186+ return "EcdsaSecp256k1VerificationKey2019"
187187+}
188188+189189+// Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation:
190190+//
191191+// - compressed / compacted binary representation
192192+// - prefix with appropriate curve multicodec bytes
193193+// - encode bytes with base58btc
194194+// - add "z" prefix to indicate encoding
195195+// - add "did:key:" prefix
196196+func (k *PublicKeyK256) DidKey() string {
197197+ return "did:key:" + k.Multibase()
198198+}
199199+200200+// Returns multibase string encoding of the public key, as would be included in an older DID Document "verificationMethod" section:
201201+//
202202+// - non-compressed / non-compacted binary representation
203203+// - encode bytes with base58btc
204204+// - prefix "z" (lower-case) to indicate encoding
205205+func (k *PublicKeyK256) LegacyMultibase() string {
206206+ kbytes := k.UncompressedBytes()
207207+ return "z" + base58.Encode(kbytes)
208208+}
+33-403
atproto/crypto/keys.go
···11package crypto
2233import (
44- "crypto"
55- "crypto/ecdh"
66- "crypto/ecdsa"
77- "crypto/elliptic"
88- "crypto/rand"
99- "crypto/sha256"
1010- "crypto/x509"
114 "fmt"
1212- "math/big"
135 "strings"
146157 "github.com/mr-tron/base58"
1616- secp256k1 "gitlab.com/yawning/secp256k1-voi"
1717- secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
188)
1992020-// 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- }
1010+type PrivateKey interface {
1111+ Public() (PublicKey, error)
1212+ HashAndSign(content []byte) ([]byte, error)
13613}
13714138138-// 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- }
1515+type PrivateKeyExportable interface {
1616+ Bytes() []byte
1717+ Equal(other PrivateKeyExportable) bool
1818+ Public() (PublicKey, error)
1919+ HashAndSign(content []byte) ([]byte, error)
15420}
15521156156-// 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.Bytes] method. This is the "compressed" curve format.
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 ParsePublicBytes(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- }
2222+type PublicKey interface {
2323+ UncompressedBytes() []byte
2424+ Bytes() []byte
2525+ Equal(other PublicKey) bool
2626+ HashAndVerify(content, sig []byte) error
2727+ Multibase() string
2828+ DidKey() string
22929230230- 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.UncompressedBytes] 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- }
3030+ // these are likely to be deprecated
3131+ LegacyDidDocSuite() string
3232+ LegacyMultibase() string
27833}
2793428035// Parses a public key in multibase encoding, as would be found in a older DID Document `verificationMethod` section.
28136//
28237// 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 ParsePublicLegacyMultibase(encoded string, kt KeyType) (*PublicKey, error) {
3838+//
3939+// This function is deprecated!
4040+func ParsePublicLegacyMultibase(encoded string, didDocSuite string) (PublicKey, error) {
28441 if len(encoded) < 2 || encoded[0] != 'z' {
28542 return nil, fmt.Errorf("crypto: not a multibase base58btc string")
28643 }
···28845 if err != nil {
28946 return nil, fmt.Errorf("crypto: not a multibase base58btc string")
29047 }
291291- return ParsePublicUncompressedBytes(data, kt)
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+ }
29256}
2935729458// Parses a public key from multibase encoding, with multicodec indicating the key type.
295295-func ParsePublicMultibase(encoded string) (*PublicKey, error) {
5959+func ParsePublicMultibase(encoded string) (PublicKey, error) {
29660 if len(encoded) < 2 || encoded[0] != 'z' {
29761 return nil, fmt.Errorf("crypto: not a multibase base58btc string")
29862 }
···30569 }
30670 if data[0] == 0x80 && data[1] == 0x24 {
30771 // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24]
308308- return ParsePublicBytes(data[2:], P256)
7272+ return ParsePublicBytesP256(data[2:])
30973 } else if data[0] == 0xE7 && data[1] == 0x01 {
31074 // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01]
311311- return ParsePublicBytes(data[2:], K256)
7575+ return ParsePublicBytesK256(data[2:])
31276 } else {
31377 return nil, fmt.Errorf("unexpected multicode code for multibase-encoded key")
31478 }
···31781// Loads a [PublicKey] from did:key string serialization.
31882//
31983// The did:key format encodes the key type.
320320-func ParsePublicDidKey(didKey string) (*PublicKey, error) {
8484+func ParsePublicDidKey(didKey string) (PublicKey, error) {
32185 if !strings.HasPrefix(didKey, "did:key:z") {
32286 return nil, fmt.Errorf("string is not a DID key: %s", didKey)
32387 }
32488 mb := strings.TrimPrefix(didKey, "did:key:")
32589 return ParsePublicMultibase(mb)
32690}
327327-328328-// Serializes the [PublicKey] in to "uncompressed" binary format.
329329-func (k *PublicKey) UncompressedBytes() []byte {
330330- switch k.keyType {
331331- case P256:
332332- pkEcdh, err := k.pubP256.ECDH()
333333- if err != nil {
334334- panic("unexpected invalid P-256/secp256r1 public key (internal)")
335335- }
336336- return pkEcdh.Bytes()
337337- case K256:
338338- p := k.pubK256.Point()
339339- // NOTE: is this check necessary for uncompressed bytes? came from go-did
340340- if p.IsIdentity() != 0 {
341341- panic("unexpected invalid K-256/secp256k1 public key (internal)")
342342- }
343343- return p.UncompressedBytes()
344344- default:
345345- panic("unexpected crypto KeyType")
346346- }
347347-}
348348-349349-// Serializes the [PublicKey] in to "compressed" binary format.
350350-func (k *PublicKey) Bytes() []byte {
351351- switch k.keyType {
352352- case P256:
353353- if !k.pubP256.Curve.IsOnCurve(k.pubP256.X, k.pubP256.Y) {
354354- panic("unexpected invalid P-256/secp256r1 public key (internal)")
355355- }
356356- return elliptic.MarshalCompressed(k.pubP256.Curve, k.pubP256.X, k.pubP256.Y)
357357- case K256:
358358- p := k.pubK256.Point()
359359- if p.IsIdentity() != 0 {
360360- panic("unexpected invalid K-256/secp256k1 public key (internal)")
361361- }
362362- return p.CompressedBytes()
363363- default:
364364- panic("unexpected crypto KeyType")
365365- }
366366-}
367367-368368-// First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure.
369369-//
370370-// 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.
371371-//
372372-// Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function.
373373-//
374374-// This method requires a "low-S" signature, as specified by atproto.
375375-func (k *PublicKey) HashAndVerify(content, sig []byte) error {
376376- hash := sha256.Sum256(content)
377377- switch k.keyType {
378378- case P256:
379379- // parseP256Sig
380380- if len(sig) != 64 {
381381- return fmt.Errorf("crypto: P-256 signatures must be 64 bytes, got len=%d", len(sig))
382382- }
383383- r := big.NewInt(0)
384384- s := big.NewInt(0)
385385- r.SetBytes(sig[:32])
386386- s.SetBytes(sig[32:])
387387-388388- if !ecdsa.Verify(k.pubP256, hash[:], r, s) {
389389- return fmt.Errorf("crypto: invalid signature")
390390- }
391391-392392- // ensure that signature is low-S
393393- if !sigSIsLowS_P256(s) {
394394- return fmt.Errorf("crypto: invalid signature (high-S P-256)")
395395- }
396396-397397- return nil
398398- case K256:
399399- if !k.pubK256.Verify(hash[:], sig, k256Options) {
400400- return fmt.Errorf("crypto: invalid signature")
401401- }
402402- return nil
403403- default:
404404- return fmt.Errorf("unexpected crypto KeyType")
405405- }
406406-}
407407-408408-// Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation:
409409-//
410410-// - compressed / compacted binary representation
411411-// - prefix with appropriate curve multicodec bytes
412412-// - encode bytes with base58btc
413413-// - add "z" prefix to indicate encoding
414414-// - add "did:key:" prefix
415415-func (k *PublicKey) DidKey() string {
416416- return "did:key:" + k.Multibase()
417417-}
418418-419419-// Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization
420420-func (k *PublicKey) Multibase() string {
421421- kbytes := k.Bytes()
422422- switch k.keyType {
423423- case P256:
424424- // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24]
425425- kbytes = append([]byte{0x80, 0x24}, kbytes...)
426426- case K256:
427427- // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01]
428428- kbytes = append([]byte{0xE7, 0x01}, kbytes...)
429429- default:
430430- panic("unexpected crypto KeyType")
431431- }
432432- return "z" + base58.Encode(kbytes)
433433-}
434434-435435-// Returns multibase string encoding of the public key, as would be included in an older DID Document "verificationMethod" section:
436436-//
437437-// - non-compressed / non-compacted binary representation
438438-// - encode bytes with base58btc
439439-// - prefix "z" (lower-case) to indicate encoding
440440-func (k *PublicKey) LegacyMultibase() string {
441441- kbytes := k.UncompressedBytes()
442442- return "z" + base58.Encode(kbytes)
443443-}
444444-445445-func (k *PublicKey) KeyType() KeyType {
446446- return k.keyType
447447-}
448448-449449-// Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`.
450450-func (k *PublicKey) LegacyDidDocSuite() string {
451451- switch k.keyType {
452452- case P256:
453453- return "EcdsaSecp256r1VerificationKey2019"
454454- case K256:
455455- // NOTE: this is not a W3C standard suite, and will probably be replaced with "Multikey"
456456- return "EcdsaSecp256k1VerificationKey2019"
457457- default:
458458- panic("unexpected crypto KeyType")
459459- }
460460-}
···11+package crypto
22+33+import (
44+ "crypto/ecdh"
55+ "crypto/ecdsa"
66+ "crypto/elliptic"
77+ "crypto/rand"
88+ "crypto/sha256"
99+ "crypto/x509"
1010+ "fmt"
1111+ "math/big"
1212+1313+ "github.com/mr-tron/base58"
1414+)
1515+1616+// P-256 / secp256r1 / ES256
1717+type PrivateKeyP256 struct {
1818+ privP256 *ecdsa.PrivateKey
1919+}
2020+2121+type PublicKeyP256 struct {
2222+ pubP256 *ecdsa.PublicKey
2323+}
2424+2525+// Creates a secure new cryptographic key from scratch, with the indicated curve type.
2626+func GeneratePrivateKeyP256() (*PrivateKeyP256, error) {
2727+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
2828+ if err != nil {
2929+ return nil, fmt.Errorf("P-256/secp256r1 key generation failed: %w", err)
3030+ }
3131+ priv := PrivateKeyP256{privP256: key}
3232+ err = priv.ensureBytes()
3333+ if err != nil {
3434+ return nil, err
3535+ }
3636+ return &priv, nil
3737+}
3838+3939+// Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method.
4040+//
4141+// 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.
4242+func ParsePrivateBytesP256(data []byte) (*PrivateKeyP256, error) {
4343+ // elaborately parse as an ecdh.PrivateKey, then get from that to ecdsa.PrivateKey by encoding/decoding using x509 PKCS8 encoding.
4444+ // Note that the 'data' bytes format is *not* x509 PKCS8!
4545+ skEcdh, err := ecdh.P256().NewPrivateKey(data)
4646+ if err != nil {
4747+ return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err)
4848+ }
4949+ enc, err := x509.MarshalPKCS8PrivateKey(skEcdh)
5050+ if err != nil {
5151+ return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err)
5252+ }
5353+ sk, err := x509.ParsePKCS8PrivateKey(enc)
5454+ if err != nil {
5555+ return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err)
5656+ }
5757+ priv := PrivateKeyP256{privP256: sk.(*ecdsa.PrivateKey)}
5858+ err = priv.ensureBytes()
5959+ if err != nil {
6060+ return nil, err
6161+ }
6262+ return &priv, nil
6363+}
6464+6565+// 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 {
6767+ otherP256, ok := other.(*PrivateKeyP256)
6868+ if ok {
6969+ return k.privP256.Equal(otherP256.privP256)
7070+ }
7171+ return false
7272+}
7373+7474+// 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+8484+// internal helper which checks that they key will be possible to export later
8585+func (k *PrivateKeyP256) ensureBytes() error {
8686+ _, err := k.privP256.ECDH()
8787+ return err
8888+}
8989+9090+// Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes].
9191+//
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.
9393+func (k *PrivateKeyP256) Bytes() []byte {
9494+ skEcdh, err := k.privP256.ECDH()
9595+ if err != nil {
9696+ panic("unexpected failure to export P-256 private key, after being exportable at parse time")
9797+ }
9898+ return skEcdh.Bytes()
9999+}
100100+101101+// First hashes the raw bytes, then signs the digest, returning a binary signature.
102102+//
103103+// 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.
104104+//
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.
106106+//
107107+// 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.
108108+func (k *PrivateKeyP256) HashAndSign(content []byte) ([]byte, error) {
109109+ hash := sha256.Sum256(content)
110110+ r, s, err := ecdsa.Sign(rand.Reader, k.privP256, hash[:])
111111+ if err != nil {
112112+ return nil, fmt.Errorf("crypto error signing with P-256/secp256r1 private key: %w", err)
113113+ }
114114+ s = sigSToLowS_P256(s)
115115+ sig := make([]byte, 64)
116116+ r.FillBytes(sig[:32])
117117+ s.FillBytes(sig[32:])
118118+ return sig, nil
119119+}
120120+121121+// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format.
122122+//
123123+// 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.
124124+func ParsePublicBytesP256(data []byte) (*PublicKeyP256, error) {
125125+ curve := elliptic.P256()
126126+ x, y := elliptic.UnmarshalCompressed(curve, data)
127127+ if x == nil {
128128+ return nil, fmt.Errorf("invalid P-256 public key (x==nil)")
129129+ }
130130+ if !curve.Params().IsOnCurve(x, y) {
131131+ return nil, fmt.Errorf("invalid P-256 public key (not on curve)")
132132+ }
133133+ pubK := &ecdsa.PublicKey{
134134+ Curve: curve,
135135+ X: x,
136136+ Y: y,
137137+ }
138138+ pub := PublicKeyP256{pubP256: pubK}
139139+ err := pub.ensureBytes()
140140+ if err != nil {
141141+ return nil, err
142142+ }
143143+ return &pub, nil
144144+}
145145+146146+// Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method.
147147+//
148148+// 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.
149149+func ParsePublicUncompressedBytesP256(data []byte) (*PublicKeyP256, error) {
150150+ curve := elliptic.P256()
151151+ x, y := elliptic.Unmarshal(curve, data)
152152+ if x == nil {
153153+ return nil, fmt.Errorf("invalid P-256 public key (x==nil)")
154154+ }
155155+ if !curve.Params().IsOnCurve(x, y) {
156156+ return nil, fmt.Errorf("invalid P-256 public key (not on curve)")
157157+ }
158158+ pubK := &ecdsa.PublicKey{
159159+ Curve: curve,
160160+ X: x,
161161+ Y: y,
162162+ }
163163+ pub := PublicKeyP256{pubP256: pubK}
164164+ err := pub.ensureBytes()
165165+ if err != nil {
166166+ return nil, err
167167+ }
168168+ return &pub, nil
169169+}
170170+171171+// Checks if the two public keys are the same. Note that the naive == operator does not work for most equality checks.
172172+func (k *PublicKeyP256) Equal(other PublicKey) bool {
173173+ otherP256, ok := other.(*PublicKeyP256)
174174+ if ok {
175175+ return k.pubP256.Equal(otherP256.pubP256)
176176+ }
177177+ return false
178178+}
179179+180180+// checks that key will be exportable later, both compressed and uncompressed
181181+func (k *PublicKeyP256) ensureBytes() error {
182182+ if !k.pubP256.Curve.IsOnCurve(k.pubP256.X, k.pubP256.Y) {
183183+ return fmt.Errorf("unexpected invalid P-256/secp256r1 public key (internal)")
184184+ }
185185+ _, err := k.pubP256.ECDH()
186186+ return err
187187+}
188188+189189+// Serializes the [PublicKey] in to "uncompressed" binary format.
190190+func (k *PublicKeyP256) UncompressedBytes() []byte {
191191+ pkEcdh, err := k.pubP256.ECDH()
192192+ if err != nil {
193193+ panic("unexpected invalid P-256/secp256r1 public key, was verified at parse time")
194194+ }
195195+ return pkEcdh.Bytes()
196196+}
197197+198198+// Serializes the [PublicKey] in to "compressed" binary format.
199199+func (k *PublicKeyP256) Bytes() []byte {
200200+ return elliptic.MarshalCompressed(k.pubP256.Curve, k.pubP256.X, k.pubP256.Y)
201201+}
202202+203203+// First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure.
204204+//
205205+// 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.
206206+//
207207+// Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function.
208208+//
209209+// This method requires a "low-S" signature, as specified by atproto.
210210+func (k *PublicKeyP256) HashAndVerify(content, sig []byte) error {
211211+ hash := sha256.Sum256(content)
212212+ // parseP256Sig
213213+ if len(sig) != 64 {
214214+ return fmt.Errorf("crypto: P-256 signatures must be 64 bytes, got len=%d", len(sig))
215215+ }
216216+ r := big.NewInt(0)
217217+ s := big.NewInt(0)
218218+ r.SetBytes(sig[:32])
219219+ s.SetBytes(sig[32:])
220220+221221+ if !ecdsa.Verify(k.pubP256, hash[:], r, s) {
222222+ return fmt.Errorf("crypto: invalid signature")
223223+ }
224224+225225+ // ensure that signature is low-S
226226+ if !sigSIsLowS_P256(s) {
227227+ return fmt.Errorf("crypto: invalid signature (high-S P-256)")
228228+ }
229229+230230+ return nil
231231+}
232232+233233+// Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization
234234+func (k *PublicKeyP256) Multibase() string {
235235+ kbytes := k.Bytes()
236236+ // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24]
237237+ kbytes = append([]byte{0x80, 0x24}, kbytes...)
238238+ return "z" + base58.Encode(kbytes)
239239+}
240240+241241+// Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`.
242242+func (k *PublicKeyP256) LegacyDidDocSuite() string {
243243+ return "EcdsaSecp256r1VerificationKey2019"
244244+}
245245+246246+// Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation:
247247+//
248248+// - compressed / compacted binary representation
249249+// - prefix with appropriate curve multicodec bytes
250250+// - encode bytes with base58btc
251251+// - add "z" prefix to indicate encoding
252252+// - add "did:key:" prefix
253253+func (k *PublicKeyP256) DidKey() string {
254254+ return "did:key:" + k.Multibase()
255255+}
256256+257257+// Returns multibase string encoding of the public key, as would be included in an older DID Document "verificationMethod" section:
258258+//
259259+// - non-compressed / non-compacted binary representation
260260+// - encode bytes with base58btc
261261+// - prefix "z" (lower-case) to indicate encoding
262262+func (k *PublicKeyP256) LegacyMultibase() string {
263263+ kbytes := k.UncompressedBytes()
264264+ return "z" + base58.Encode(kbytes)
265265+}