this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

crypto: refactor to interface-based API

+592 -460
+5 -2
atproto/crypto/examples_test.go
··· 27 27 28 28 func ExamplePrivateKey() { 29 29 // create secure private key, and corresponding public key 30 - priv, err := GeneratePrivateKey(K256) 30 + priv, err := GeneratePrivateKeyK256() 31 31 if err != nil { 32 32 panic("failed to generate key") 33 33 } 34 - pub := priv.Public() 34 + pub, err := priv.Public() 35 + if err != nil { 36 + panic("failed to get public key") 37 + } 35 38 36 39 // sign a message 37 40 msg := []byte("hello world")
+12 -12
atproto/crypto/interop_fixtures_test.go
··· 50 50 func testSignatureFixture(t *testing.T, row InteropFixture) { 51 51 assert := assert.New(t) 52 52 53 - var kt KeyType 54 - switch row.DidDocSuite { 55 - case "EcdsaSecp256r1VerificationKey2019": 56 - kt = P256 57 - case "EcdsaSecp256k1VerificationKey2019": 58 - kt = K256 59 - default: 60 - t.Fatal("expected DidDocSuite") 61 - } 62 - 63 53 // parse all the fields 64 54 pkDid, err := ParsePublicDidKey(row.PublicKeyDid) 65 55 assert.NoError(err) 66 56 keyBytes, err := base58.Decode(row.PublicKeyMultibase[1:]) 67 - assert.NoError(err) 68 - pkCompMultibase, err := ParsePublicBytes(keyBytes, kt) 69 57 assert.NoError(err) 70 58 msgBytes, err := base64.RawStdEncoding.DecodeString(row.MessageBase64) 71 59 assert.NoError(err) 72 60 sigBytes, err := base64.RawStdEncoding.DecodeString(row.SignatureBase64) 73 61 assert.NoError(err) 62 + 63 + var pkCompMultibase PublicKey 64 + switch row.DidDocSuite { 65 + case "EcdsaSecp256r1VerificationKey2019": 66 + pkCompMultibase, err = ParsePublicBytesP256(keyBytes) 67 + assert.NoError(err) 68 + case "EcdsaSecp256k1VerificationKey2019": 69 + pkCompMultibase, err = ParsePublicBytesK256(keyBytes) 70 + assert.NoError(err) 71 + default: 72 + t.Fatal("expected DidDocSuite") 73 + } 74 74 75 75 // verify encodings 76 76 assert.Equal(pkDid, pkCompMultibase, "key equality")
+208
atproto/crypto/k256.go
··· 1 + package crypto 2 + 3 + import ( 4 + "crypto" 5 + "crypto/rand" 6 + "crypto/sha256" 7 + "fmt" 8 + 9 + "github.com/mr-tron/base58" 10 + secp256k1 "gitlab.com/yawning/secp256k1-voi" 11 + secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec" 12 + ) 13 + 14 + // K-256 / secp256k1 / ES256K 15 + type PrivateKeyK256 struct { 16 + privK256 *secp256k1secec.PrivateKey 17 + } 18 + 19 + type PublicKeyK256 struct { 20 + pubK256 *secp256k1secec.PublicKey 21 + } 22 + 23 + var k256Options = &secp256k1secec.ECDSAOptions{ 24 + // Used to *verify* digest, not to re-hash 25 + Hash: crypto.SHA256, 26 + // Use `[R | S]` encoding. 27 + Encoding: secp256k1secec.EncodingCompact, 28 + // 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. 29 + RejectMalleable: true, 30 + } 31 + 32 + // Creates a secure new cryptographic key from scratch, with the indicated curve type. 33 + func GeneratePrivateKeyK256() (*PrivateKeyK256, error) { 34 + key, err := secp256k1secec.GenerateKey() 35 + if err != nil { 36 + return nil, fmt.Errorf("K-256/secp256k1 key generation failed: %w", err) 37 + } 38 + return &PrivateKeyK256{privK256: key}, nil 39 + } 40 + 41 + // Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method. (XXX) 42 + // 43 + // 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. 44 + func ParsePrivateBytesK256(data []byte) (*PrivateKeyK256, error) { 45 + sk, err := secp256k1secec.NewPrivateKey(data) 46 + if err != nil { 47 + return nil, fmt.Errorf("invalid K-256/secp256k1 private key: %w", err) 48 + } 49 + return &PrivateKeyK256{privK256: sk}, nil 50 + } 51 + 52 + // Checks if the two private keys are the same. Note that the naive == operator does not work for most equality checks. 53 + func (k *PrivateKeyK256) Equal(other PrivateKeyExportable) bool { 54 + otherK256, ok := other.(*PrivateKeyK256) 55 + if ok { 56 + return k.privK256.Equal(otherK256.privK256) 57 + } 58 + return false 59 + } 60 + 61 + // Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes]. 62 + // 63 + // 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. 64 + func (k *PrivateKeyK256) Bytes() []byte { 65 + return k.privK256.Bytes() 66 + } 67 + 68 + // Outputs the PublicKey corresponding to this PrivateKey. 69 + func (k *PrivateKeyK256) Public() (PublicKey, error) { 70 + pub := PublicKeyK256{pubK256: k.privK256.PublicKey()} 71 + err := pub.ensureBytes() 72 + if err != nil { 73 + return nil, err 74 + } 75 + return &pub, nil 76 + } 77 + 78 + // First hashes the raw bytes, then signs the digest, returning a binary signature. 79 + // 80 + // 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. 81 + // 82 + // 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. 83 + // 84 + // 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. 85 + func (k *PrivateKeyK256) HashAndSign(content []byte) ([]byte, error) { 86 + hash := sha256.Sum256(content) 87 + return k.privK256.Sign(rand.Reader, hash[:], k256Options) 88 + } 89 + 90 + // Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format. 91 + // 92 + // 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. 93 + func ParsePublicBytesK256(data []byte) (*PublicKeyK256, error) { 94 + // secp256k1secec.NewPublicKey accepts any valid encoding, while we 95 + // explicitly want compressed, so use the explicit point 96 + // decompression routine. 97 + p, err := secp256k1.NewIdentityPoint().SetCompressedBytes(data) 98 + if err != nil { 99 + return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err) 100 + } 101 + 102 + pubK, err := secp256k1secec.NewPublicKeyFromPoint(p) 103 + if err != nil { 104 + return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err) 105 + } 106 + pub := PublicKeyK256{pubK256: pubK} 107 + err = pub.ensureBytes() 108 + if err != nil { 109 + return nil, err 110 + } 111 + return &pub, nil 112 + } 113 + 114 + // Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method. 115 + // 116 + // 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. 117 + func ParsePublicUncompressedBytesK256(data []byte) (*PublicKeyK256, error) { 118 + pubK, err := secp256k1secec.NewPublicKey(data) 119 + if err != nil { 120 + return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err) 121 + } 122 + pub := PublicKeyK256{pubK256: pubK} 123 + err = pub.ensureBytes() 124 + if err != nil { 125 + return nil, err 126 + } 127 + return &pub, nil 128 + } 129 + 130 + // Checks if the two public keys are the same. Note that the naive == operator does not work for most equality checks. 131 + func (k *PublicKeyK256) Equal(other PublicKey) bool { 132 + otherK256, ok := other.(*PublicKeyK256) 133 + if ok { 134 + return k.pubK256.Equal(otherK256.pubK256) 135 + } 136 + return false 137 + } 138 + 139 + // verifies that this public key is safe to export as bytes later on 140 + func (k *PublicKeyK256) ensureBytes() error { 141 + p := k.pubK256.Point() 142 + if p.IsIdentity() != 0 { 143 + return fmt.Errorf("unexpected invalid K-256/secp256k1 public key (internal)") 144 + } 145 + return nil 146 + } 147 + 148 + // Serializes the [PublicKey] in to "uncompressed" binary format. 149 + func (k *PublicKeyK256) UncompressedBytes() []byte { 150 + p := k.pubK256.Point() 151 + return p.UncompressedBytes() 152 + } 153 + 154 + // Serializes the [PublicKey] in to "compressed" binary format. 155 + func (k *PublicKeyK256) Bytes() []byte { 156 + p := k.pubK256.Point() 157 + return p.CompressedBytes() 158 + } 159 + 160 + // First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure. 161 + // 162 + // 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. 163 + // 164 + // Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function. 165 + // 166 + // This method requires a "low-S" signature, as specified by atproto. 167 + func (k *PublicKeyK256) HashAndVerify(content, sig []byte) error { 168 + hash := sha256.Sum256(content) 169 + if !k.pubK256.Verify(hash[:], sig, k256Options) { 170 + return fmt.Errorf("crypto: invalid signature") 171 + } 172 + return nil 173 + } 174 + 175 + // Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization 176 + func (k *PublicKeyK256) Multibase() string { 177 + kbytes := k.Bytes() 178 + // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01] 179 + kbytes = append([]byte{0xE7, 0x01}, kbytes...) 180 + return "z" + base58.Encode(kbytes) 181 + } 182 + 183 + // Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`. 184 + func (k *PublicKeyK256) LegacyDidDocSuite() string { 185 + // NOTE: this is not a W3C standard suite, and will probably be replaced with "Multikey" 186 + return "EcdsaSecp256k1VerificationKey2019" 187 + } 188 + 189 + // Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation: 190 + // 191 + // - compressed / compacted binary representation 192 + // - prefix with appropriate curve multicodec bytes 193 + // - encode bytes with base58btc 194 + // - add "z" prefix to indicate encoding 195 + // - add "did:key:" prefix 196 + func (k *PublicKeyK256) DidKey() string { 197 + return "did:key:" + k.Multibase() 198 + } 199 + 200 + // Returns multibase string encoding of the public key, as would be included in an older DID Document "verificationMethod" section: 201 + // 202 + // - non-compressed / non-compacted binary representation 203 + // - encode bytes with base58btc 204 + // - prefix "z" (lower-case) to indicate encoding 205 + func (k *PublicKeyK256) LegacyMultibase() string { 206 + kbytes := k.UncompressedBytes() 207 + return "z" + base58.Encode(kbytes) 208 + }
+33 -403
atproto/crypto/keys.go
··· 1 1 package crypto 2 2 3 3 import ( 4 - "crypto" 5 - "crypto/ecdh" 6 - "crypto/ecdsa" 7 - "crypto/elliptic" 8 - "crypto/rand" 9 - "crypto/sha256" 10 - "crypto/x509" 11 4 "fmt" 12 - "math/big" 13 5 "strings" 14 6 15 7 "github.com/mr-tron/base58" 16 - secp256k1 "gitlab.com/yawning/secp256k1-voi" 17 - secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec" 18 8 ) 19 9 20 - // Represents the specific support curve type. It is not possible to use [elliptic.Curve] for this because some curves are not in stdlib 21 - type KeyType uint8 22 - 23 - const ( 24 - P256 KeyType = 1 // P-256 / secp256r1 / ES256 25 - K256 KeyType = 2 // K-256 / secp256k1 / ES256K 26 - ) 27 - 28 - type PrivateKey struct { 29 - keyType KeyType 30 - privP256 *ecdsa.PrivateKey 31 - privK256 *secp256k1secec.PrivateKey 32 - } 33 - 34 - type PublicKey struct { 35 - keyType KeyType 36 - pubP256 *ecdsa.PublicKey 37 - pubK256 *secp256k1secec.PublicKey 38 - } 39 - 40 - var k256Options = &secp256k1secec.ECDSAOptions{ 41 - // Used to *verify* digest, not to re-hash 42 - Hash: crypto.SHA256, 43 - // Use `[R | S]` encoding. 44 - Encoding: secp256k1secec.EncodingCompact, 45 - // 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. 46 - RejectMalleable: true, 47 - } 48 - 49 - // Creates a secure new cryptographic key from scratch, with the indicated curve type. 50 - func GeneratePrivateKey(kt KeyType) (*PrivateKey, error) { 51 - switch kt { 52 - case P256: 53 - key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 54 - if err != nil { 55 - return nil, fmt.Errorf("P-256/secp256r1 key generation failed: %w", err) 56 - } 57 - return &PrivateKey{keyType: kt, privP256: key}, nil 58 - case K256: 59 - key, err := secp256k1secec.GenerateKey() 60 - if err != nil { 61 - return nil, fmt.Errorf("K-256/secp256k1 key generation failed: %w", err) 62 - } 63 - return &PrivateKey{keyType: kt, privK256: key}, nil 64 - default: 65 - return nil, fmt.Errorf("unexpected crypto KeyType") 66 - } 67 - } 68 - 69 - // Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method. 70 - // 71 - // 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. 72 - func ParsePrivateKeyBytes(data []byte, kt KeyType) (*PrivateKey, error) { 73 - switch kt { 74 - case P256: 75 - // elaborately parse as an ecdh.PrivateKey, then get from that to ecdsa.PrivateKey by encoding/decoding using x509 PKCS8 encoding. 76 - // Note that the 'data' bytes format is *not* x509 PKCS8! 77 - skEcdh, err := ecdh.P256().NewPrivateKey(data) 78 - if err != nil { 79 - return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err) 80 - } 81 - enc, err := x509.MarshalPKCS8PrivateKey(skEcdh) 82 - if err != nil { 83 - return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err) 84 - } 85 - sk, err := x509.ParsePKCS8PrivateKey(enc) 86 - if err != nil { 87 - return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err) 88 - } 89 - return &PrivateKey{keyType: kt, privP256: sk.(*ecdsa.PrivateKey)}, nil 90 - case K256: 91 - sk, err := secp256k1secec.NewPrivateKey(data) 92 - if err != nil { 93 - return nil, fmt.Errorf("invalid K-256/secp256k1 private key: %w", err) 94 - } 95 - return &PrivateKey{keyType: kt, privK256: sk}, nil 96 - default: 97 - return nil, fmt.Errorf("unexpected crypto KeyType") 98 - } 99 - } 100 - 101 - // Checks if the two private keys are the same. Note that the naive == operator does not work for most equality checks. 102 - func (k *PrivateKey) Equal(other *PrivateKey) bool { 103 - if k.keyType != other.keyType { 104 - return false 105 - } 106 - switch k.keyType { 107 - case P256: 108 - return k.privP256.Equal(other.privP256) 109 - case K256: 110 - return k.privK256.Equal(other.privK256) 111 - default: 112 - panic("unexpected crypto KeyType") 113 - } 114 - } 115 - 116 - func (k *PrivateKey) KeyType() KeyType { 117 - return k.keyType 118 - } 119 - 120 - // Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes]. 121 - // 122 - // 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. 123 - func (k *PrivateKey) Bytes() ([]byte, error) { 124 - switch k.keyType { 125 - case P256: 126 - skEcdh, err := k.privP256.ECDH() 127 - if err != nil { 128 - return nil, fmt.Errorf("unexpected failure to convert key type: %w", err) 129 - } 130 - return skEcdh.Bytes(), nil 131 - case K256: 132 - return k.privK256.Bytes(), nil 133 - default: 134 - return nil, fmt.Errorf("unexpected crypto KeyType") 135 - } 10 + type PrivateKey interface { 11 + Public() (PublicKey, error) 12 + HashAndSign(content []byte) ([]byte, error) 136 13 } 137 14 138 - // Outputs the PublicKey corresponding to this PrivateKey. 139 - func (k *PrivateKey) Public() PublicKey { 140 - switch k.keyType { 141 - case P256: 142 - return PublicKey{ 143 - keyType: k.keyType, 144 - pubP256: k.privP256.Public().(*ecdsa.PublicKey), 145 - } 146 - case K256: 147 - return PublicKey{ 148 - keyType: k.keyType, 149 - pubK256: k.privK256.PublicKey(), 150 - } 151 - default: 152 - panic("unexpected crypto KeyType") 153 - } 15 + type PrivateKeyExportable interface { 16 + Bytes() []byte 17 + Equal(other PrivateKeyExportable) bool 18 + Public() (PublicKey, error) 19 + HashAndSign(content []byte) ([]byte, error) 154 20 } 155 21 156 - // First hashes the raw bytes, then signs the digest, returning a binary signature. 157 - // 158 - // 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. 159 - // 160 - // 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. 161 - // 162 - // 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. 163 - func (k *PrivateKey) HashAndSign(content []byte) ([]byte, error) { 164 - hash := sha256.Sum256(content) 165 - switch k.keyType { 166 - case P256: 167 - r, s, err := ecdsa.Sign(rand.Reader, k.privP256, hash[:]) 168 - if err != nil { 169 - return nil, fmt.Errorf("crypto error signing with P-256/secp256r1 private key: %w", err) 170 - } 171 - s = sigSToLowS_P256(s) 172 - sig := make([]byte, 64) 173 - r.FillBytes(sig[:32]) 174 - s.FillBytes(sig[32:]) 175 - return sig, nil 176 - case K256: 177 - return k.privK256.Sign(rand.Reader, hash[:], k256Options) 178 - default: 179 - return nil, fmt.Errorf("unexpected crypto KeyType") 180 - } 181 - } 182 - 183 - // Checks if the two public keys are the same. Note that the naive == operator does not work for most equality checks. 184 - func (k *PublicKey) Equal(other *PublicKey) bool { 185 - if k.keyType != other.keyType { 186 - return false 187 - } 188 - switch k.keyType { 189 - case P256: 190 - return k.pubP256.Equal(other.pubP256) 191 - case K256: 192 - return k.pubK256.Equal(other.pubK256) 193 - default: 194 - panic("unexpected crypto KeyType") 195 - } 196 - } 197 - 198 - // Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format. 199 - // 200 - // 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. 201 - func ParsePublicBytes(data []byte, kt KeyType) (*PublicKey, error) { 202 - switch kt { 203 - case P256: 204 - curve := elliptic.P256() 205 - x, y := elliptic.UnmarshalCompressed(curve, data) 206 - if x == nil { 207 - return nil, fmt.Errorf("invalid P-256 public key (x==nil)") 208 - } 209 - if !curve.Params().IsOnCurve(x, y) { 210 - return nil, fmt.Errorf("invalid P-256 public key (not on curve)") 211 - } 212 - pub := &ecdsa.PublicKey{ 213 - Curve: curve, 214 - X: x, 215 - Y: y, 216 - } 217 - return &PublicKey{ 218 - keyType: kt, 219 - pubP256: pub, 220 - }, nil 221 - case K256: 222 - // secp256k1secec.NewPublicKey accepts any valid encoding, while we 223 - // explicitly want compressed, so use the explicit point 224 - // decompression routine. 225 - p, err := secp256k1.NewIdentityPoint().SetCompressedBytes(data) 226 - if err != nil { 227 - return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err) 228 - } 22 + type PublicKey interface { 23 + UncompressedBytes() []byte 24 + Bytes() []byte 25 + Equal(other PublicKey) bool 26 + HashAndVerify(content, sig []byte) error 27 + Multibase() string 28 + DidKey() string 229 29 230 - pub, err := secp256k1secec.NewPublicKeyFromPoint(p) 231 - if err != nil { 232 - return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err) 233 - } 234 - return &PublicKey{ 235 - keyType: kt, 236 - pubK256: pub, 237 - }, nil 238 - default: 239 - return nil, fmt.Errorf("unexpected crypto KeyType") 240 - } 241 - } 242 - 243 - // Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method. 244 - // 245 - // 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. 246 - func ParsePublicUncompressedBytes(data []byte, kt KeyType) (*PublicKey, error) { 247 - switch kt { 248 - case P256: 249 - curve := elliptic.P256() 250 - x, y := elliptic.Unmarshal(curve, data) 251 - if x == nil { 252 - return nil, fmt.Errorf("invalid P-256 public key (x==nil)") 253 - } 254 - if !curve.Params().IsOnCurve(x, y) { 255 - return nil, fmt.Errorf("invalid P-256 public key (not on curve)") 256 - } 257 - pub := &ecdsa.PublicKey{ 258 - Curve: curve, 259 - X: x, 260 - Y: y, 261 - } 262 - return &PublicKey{ 263 - keyType: kt, 264 - pubP256: pub, 265 - }, nil 266 - case K256: 267 - pub, err := secp256k1secec.NewPublicKey(data) 268 - if err != nil { 269 - return nil, fmt.Errorf("invalid K-256/secp256k1 public key: %w", err) 270 - } 271 - return &PublicKey{ 272 - keyType: kt, 273 - pubK256: pub, 274 - }, nil 275 - default: 276 - return nil, fmt.Errorf("unexpected crypto KeyType") 277 - } 30 + // these are likely to be deprecated 31 + LegacyDidDocSuite() string 32 + LegacyMultibase() string 278 33 } 279 34 280 35 // Parses a public key in multibase encoding, as would be found in a older DID Document `verificationMethod` section. 281 36 // 282 37 // This implementation does not handle the many possible multibase encodings (eg, base32), only the base58btc encoding that would be found in a DID Document. 283 - func ParsePublicLegacyMultibase(encoded string, kt KeyType) (*PublicKey, error) { 38 + // 39 + // This function is deprecated! 40 + func ParsePublicLegacyMultibase(encoded string, didDocSuite string) (PublicKey, error) { 284 41 if len(encoded) < 2 || encoded[0] != 'z' { 285 42 return nil, fmt.Errorf("crypto: not a multibase base58btc string") 286 43 } ··· 288 45 if err != nil { 289 46 return nil, fmt.Errorf("crypto: not a multibase base58btc string") 290 47 } 291 - return ParsePublicUncompressedBytes(data, kt) 48 + switch didDocSuite { 49 + case "EcdsaSecp256r1VerificationKey2019": 50 + return ParsePublicUncompressedBytesP256(data) 51 + case "EcdsaSecp256k1VerificationKey2019": 52 + return ParsePublicUncompressedBytesK256(data) 53 + default: 54 + return nil, fmt.Errorf("unhandled legacy crypto suite: %s", didDocSuite) 55 + } 292 56 } 293 57 294 58 // Parses a public key from multibase encoding, with multicodec indicating the key type. 295 - func ParsePublicMultibase(encoded string) (*PublicKey, error) { 59 + func ParsePublicMultibase(encoded string) (PublicKey, error) { 296 60 if len(encoded) < 2 || encoded[0] != 'z' { 297 61 return nil, fmt.Errorf("crypto: not a multibase base58btc string") 298 62 } ··· 305 69 } 306 70 if data[0] == 0x80 && data[1] == 0x24 { 307 71 // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24] 308 - return ParsePublicBytes(data[2:], P256) 72 + return ParsePublicBytesP256(data[2:]) 309 73 } else if data[0] == 0xE7 && data[1] == 0x01 { 310 74 // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01] 311 - return ParsePublicBytes(data[2:], K256) 75 + return ParsePublicBytesK256(data[2:]) 312 76 } else { 313 77 return nil, fmt.Errorf("unexpected multicode code for multibase-encoded key") 314 78 } ··· 317 81 // Loads a [PublicKey] from did:key string serialization. 318 82 // 319 83 // The did:key format encodes the key type. 320 - func ParsePublicDidKey(didKey string) (*PublicKey, error) { 84 + func ParsePublicDidKey(didKey string) (PublicKey, error) { 321 85 if !strings.HasPrefix(didKey, "did:key:z") { 322 86 return nil, fmt.Errorf("string is not a DID key: %s", didKey) 323 87 } 324 88 mb := strings.TrimPrefix(didKey, "did:key:") 325 89 return ParsePublicMultibase(mb) 326 90 } 327 - 328 - // Serializes the [PublicKey] in to "uncompressed" binary format. 329 - func (k *PublicKey) UncompressedBytes() []byte { 330 - switch k.keyType { 331 - case P256: 332 - pkEcdh, err := k.pubP256.ECDH() 333 - if err != nil { 334 - panic("unexpected invalid P-256/secp256r1 public key (internal)") 335 - } 336 - return pkEcdh.Bytes() 337 - case K256: 338 - p := k.pubK256.Point() 339 - // NOTE: is this check necessary for uncompressed bytes? came from go-did 340 - if p.IsIdentity() != 0 { 341 - panic("unexpected invalid K-256/secp256k1 public key (internal)") 342 - } 343 - return p.UncompressedBytes() 344 - default: 345 - panic("unexpected crypto KeyType") 346 - } 347 - } 348 - 349 - // Serializes the [PublicKey] in to "compressed" binary format. 350 - func (k *PublicKey) Bytes() []byte { 351 - switch k.keyType { 352 - case P256: 353 - if !k.pubP256.Curve.IsOnCurve(k.pubP256.X, k.pubP256.Y) { 354 - panic("unexpected invalid P-256/secp256r1 public key (internal)") 355 - } 356 - return elliptic.MarshalCompressed(k.pubP256.Curve, k.pubP256.X, k.pubP256.Y) 357 - case K256: 358 - p := k.pubK256.Point() 359 - if p.IsIdentity() != 0 { 360 - panic("unexpected invalid K-256/secp256k1 public key (internal)") 361 - } 362 - return p.CompressedBytes() 363 - default: 364 - panic("unexpected crypto KeyType") 365 - } 366 - } 367 - 368 - // First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure. 369 - // 370 - // 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. 371 - // 372 - // Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function. 373 - // 374 - // This method requires a "low-S" signature, as specified by atproto. 375 - func (k *PublicKey) HashAndVerify(content, sig []byte) error { 376 - hash := sha256.Sum256(content) 377 - switch k.keyType { 378 - case P256: 379 - // parseP256Sig 380 - if len(sig) != 64 { 381 - return fmt.Errorf("crypto: P-256 signatures must be 64 bytes, got len=%d", len(sig)) 382 - } 383 - r := big.NewInt(0) 384 - s := big.NewInt(0) 385 - r.SetBytes(sig[:32]) 386 - s.SetBytes(sig[32:]) 387 - 388 - if !ecdsa.Verify(k.pubP256, hash[:], r, s) { 389 - return fmt.Errorf("crypto: invalid signature") 390 - } 391 - 392 - // ensure that signature is low-S 393 - if !sigSIsLowS_P256(s) { 394 - return fmt.Errorf("crypto: invalid signature (high-S P-256)") 395 - } 396 - 397 - return nil 398 - case K256: 399 - if !k.pubK256.Verify(hash[:], sig, k256Options) { 400 - return fmt.Errorf("crypto: invalid signature") 401 - } 402 - return nil 403 - default: 404 - return fmt.Errorf("unexpected crypto KeyType") 405 - } 406 - } 407 - 408 - // Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation: 409 - // 410 - // - compressed / compacted binary representation 411 - // - prefix with appropriate curve multicodec bytes 412 - // - encode bytes with base58btc 413 - // - add "z" prefix to indicate encoding 414 - // - add "did:key:" prefix 415 - func (k *PublicKey) DidKey() string { 416 - return "did:key:" + k.Multibase() 417 - } 418 - 419 - // Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization 420 - func (k *PublicKey) Multibase() string { 421 - kbytes := k.Bytes() 422 - switch k.keyType { 423 - case P256: 424 - // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24] 425 - kbytes = append([]byte{0x80, 0x24}, kbytes...) 426 - case K256: 427 - // multicodec secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01] 428 - kbytes = append([]byte{0xE7, 0x01}, kbytes...) 429 - default: 430 - panic("unexpected crypto KeyType") 431 - } 432 - return "z" + base58.Encode(kbytes) 433 - } 434 - 435 - // Returns multibase string encoding of the public key, as would be included in an older DID Document "verificationMethod" section: 436 - // 437 - // - non-compressed / non-compacted binary representation 438 - // - encode bytes with base58btc 439 - // - prefix "z" (lower-case) to indicate encoding 440 - func (k *PublicKey) LegacyMultibase() string { 441 - kbytes := k.UncompressedBytes() 442 - return "z" + base58.Encode(kbytes) 443 - } 444 - 445 - func (k *PublicKey) KeyType() KeyType { 446 - return k.keyType 447 - } 448 - 449 - // Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`. 450 - func (k *PublicKey) LegacyDidDocSuite() string { 451 - switch k.keyType { 452 - case P256: 453 - return "EcdsaSecp256r1VerificationKey2019" 454 - case K256: 455 - // NOTE: this is not a W3C standard suite, and will probably be replaced with "Multikey" 456 - return "EcdsaSecp256k1VerificationKey2019" 457 - default: 458 - panic("unexpected crypto KeyType") 459 - } 460 - }
+50 -35
atproto/crypto/keys_test.go
··· 7 7 "github.com/stretchr/testify/assert" 8 8 ) 9 9 10 - var keyTypes = []KeyType{K256, P256} 11 - 12 10 func TestKeyBasics(t *testing.T) { 13 11 assert := assert.New(t) 14 12 ··· 20 18 _, err = rand.Read(bigMsg) 21 19 assert.NoError(err) 22 20 23 - for _, kt := range keyTypes { 24 - // private key generation and encoding 25 - priv, err := GeneratePrivateKey(kt) 26 - assert.NoError(err) 27 - privBytes, err := priv.Bytes() 28 - assert.NoError(err) 29 - privFromBytes, err := ParsePrivateKeyBytes(privBytes, kt) 30 - assert.NoError(err) 31 - assert.Equal(priv, privFromBytes) 21 + // private key generation and byte serialization (P-256) 22 + privP256, err := GeneratePrivateKeyP256() 23 + assert.NoError(err) 24 + privP256Bytes := privP256.Bytes() 25 + privP256FromBytes, err := ParsePrivateBytesP256(privP256Bytes) 26 + assert.NoError(err) 27 + assert.Equal(privP256, privP256FromBytes) 28 + 29 + // private key generation and byte serialization (K-256) 30 + privK256, err := GeneratePrivateKeyK256() 31 + assert.NoError(err) 32 + privK256Bytes := privK256.Bytes() 33 + privK256FromBytes, err := ParsePrivateBytesK256(privK256Bytes) 34 + assert.NoError(err) 35 + assert.Equal(privK256, privK256FromBytes) 36 + 37 + // public key byte serialization (P-256) 38 + pubP256, err := privP256.Public() 39 + assert.NoError(err) 40 + pubP256CompBytes := pubP256.Bytes() 41 + pubP256FromCompBytes, err := ParsePublicBytesP256(pubP256CompBytes) 42 + assert.NoError(err) 43 + assert.True(pubP256.Equal(pubP256FromCompBytes)) 32 44 33 - // public key encoding 34 - pub := priv.Public() 45 + pubP256UncompBytes := pubP256.UncompressedBytes() 46 + pubP256FromUncompBytes, err := ParsePublicUncompressedBytesP256(pubP256UncompBytes) 47 + assert.NoError(err) 48 + assert.True(pubP256.Equal(pubP256FromUncompBytes)) 35 49 36 - pubCompBytes := pub.Bytes() 37 - pubFromCompBytes, err := ParsePublicBytes(pubCompBytes, kt) 38 - assert.NoError(err) 39 - assert.True(pub.Equal(pubFromCompBytes)) 50 + pubP256LegacyMultibaseString := pubP256.LegacyMultibase() 51 + pubP256LMB, err := ParsePublicLegacyMultibase(pubP256LegacyMultibaseString, "EcdsaSecp256r1VerificationKey2019") 52 + assert.NoError(err) 53 + assert.True(pubP256.Equal(pubP256LMB)) 40 54 41 - pubUncompBytes := pub.UncompressedBytes() 42 - pubFromUncompBytes, err := ParsePublicUncompressedBytes(pubUncompBytes, kt) 55 + both := []PrivateKey{privP256, privK256} 56 + for _, priv := range both { 57 + pub, err := priv.Public() 43 58 assert.NoError(err) 44 - assert.True(pub.Equal(pubFromUncompBytes)) 45 59 60 + // public key encoding 46 61 pubDidKeyString := pub.DidKey() 47 62 pubDK, err := ParsePublicDidKey(pubDidKeyString) 48 63 assert.NoError(err) ··· 52 67 pubMB, err := ParsePublicMultibase(pubMultibaseString) 53 68 assert.NoError(err) 54 69 assert.True(pub.Equal(pubMB)) 55 - 56 - pubLegacyMultibaseString := pub.LegacyMultibase() 57 - pubLMB, err := ParsePublicLegacyMultibase(pubLegacyMultibaseString, kt) 58 - assert.NoError(err) 59 - assert.True(pub.Equal(pubLMB)) 60 70 61 71 // signature verification 62 72 sig, err := priv.HashAndSign(msg) ··· 79 89 80 90 msg := make([]byte, 1024) 81 91 82 - for _, kt := range keyTypes { 83 - for i := 0; i < 128; i++ { 84 - priv, err := GeneratePrivateKey(kt) 92 + for i := 0; i < 128; i++ { 93 + privP256, err := GeneratePrivateKeyP256() 94 + assert.NoError(err) 95 + privK256, err := GeneratePrivateKeyK256() 96 + assert.NoError(err) 97 + 98 + both := []PrivateKey{privP256, privK256} 99 + for _, priv := range both { 100 + pub, err := priv.Public() 85 101 assert.NoError(err) 86 - pub := priv.Public() 87 102 88 103 _, err = rand.Read(msg) 89 104 assert.NoError(err) ··· 103 118 func TestKeyCompressionP256(t *testing.T) { 104 119 assert := assert.New(t) 105 120 106 - priv, err := GeneratePrivateKey(P256) 121 + priv, err := GeneratePrivateKeyP256() 107 122 assert.NoError(err) 108 - privBytes, err := priv.Bytes() 123 + privBytes := priv.Bytes() 124 + pub, err := priv.Public() 109 125 assert.NoError(err) 110 - pub := priv.Public() 111 126 sig, err := priv.HashAndSign([]byte("test-message")) 112 127 assert.NoError(err) 113 128 ··· 121 136 func TestKeyCompressionK256(t *testing.T) { 122 137 assert := assert.New(t) 123 138 124 - priv, err := GeneratePrivateKey(K256) 139 + priv, err := GeneratePrivateKeyK256() 125 140 assert.NoError(err) 126 - privBytes, err := priv.Bytes() 141 + privBytes := priv.Bytes() 142 + pub, err := priv.Public() 127 143 assert.NoError(err) 128 - pub := priv.Public() 129 144 sig, err := priv.HashAndSign([]byte("test-message")) 130 145 assert.NoError(err) 131 146
+265
atproto/crypto/p256.go
··· 1 + package crypto 2 + 3 + import ( 4 + "crypto/ecdh" 5 + "crypto/ecdsa" 6 + "crypto/elliptic" 7 + "crypto/rand" 8 + "crypto/sha256" 9 + "crypto/x509" 10 + "fmt" 11 + "math/big" 12 + 13 + "github.com/mr-tron/base58" 14 + ) 15 + 16 + // P-256 / secp256r1 / ES256 17 + type PrivateKeyP256 struct { 18 + privP256 *ecdsa.PrivateKey 19 + } 20 + 21 + type PublicKeyP256 struct { 22 + pubP256 *ecdsa.PublicKey 23 + } 24 + 25 + // Creates a secure new cryptographic key from scratch, with the indicated curve type. 26 + func GeneratePrivateKeyP256() (*PrivateKeyP256, error) { 27 + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 28 + if err != nil { 29 + return nil, fmt.Errorf("P-256/secp256r1 key generation failed: %w", err) 30 + } 31 + priv := PrivateKeyP256{privP256: key} 32 + err = priv.ensureBytes() 33 + if err != nil { 34 + return nil, err 35 + } 36 + return &priv, nil 37 + } 38 + 39 + // Loads a [PrivateKey] of the indicated curve type from raw bytes, as exported by the [PrivateKey.Bytes()] method. 40 + // 41 + // 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. 42 + func ParsePrivateBytesP256(data []byte) (*PrivateKeyP256, error) { 43 + // elaborately parse as an ecdh.PrivateKey, then get from that to ecdsa.PrivateKey by encoding/decoding using x509 PKCS8 encoding. 44 + // Note that the 'data' bytes format is *not* x509 PKCS8! 45 + skEcdh, err := ecdh.P256().NewPrivateKey(data) 46 + if err != nil { 47 + return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err) 48 + } 49 + enc, err := x509.MarshalPKCS8PrivateKey(skEcdh) 50 + if err != nil { 51 + return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err) 52 + } 53 + sk, err := x509.ParsePKCS8PrivateKey(enc) 54 + if err != nil { 55 + return nil, fmt.Errorf("invalid P-256/secp256r1 private key: %w", err) 56 + } 57 + priv := PrivateKeyP256{privP256: sk.(*ecdsa.PrivateKey)} 58 + err = priv.ensureBytes() 59 + if err != nil { 60 + return nil, err 61 + } 62 + return &priv, nil 63 + } 64 + 65 + // Checks if the two private keys are the same. Note that the naive == operator does not work for most equality checks. 66 + func (k *PrivateKeyP256) Equal(other PrivateKeyExportable) bool { 67 + otherP256, ok := other.(*PrivateKeyP256) 68 + if ok { 69 + return k.privP256.Equal(otherP256.privP256) 70 + } 71 + return false 72 + } 73 + 74 + // Outputs the PublicKey corresponding to this PrivateKey. 75 + func (k *PrivateKeyP256) Public() (PublicKey, error) { 76 + pub := PublicKeyP256{pubP256: k.privP256.Public().(*ecdsa.PublicKey)} 77 + err := pub.ensureBytes() 78 + if err != nil { 79 + return nil, err 80 + } 81 + return &pub, nil 82 + } 83 + 84 + // internal helper which checks that they key will be possible to export later 85 + func (k *PrivateKeyP256) ensureBytes() error { 86 + _, err := k.privP256.ECDH() 87 + return err 88 + } 89 + 90 + // Serializes the secret key material in to a raw binary format, which can be parsed by [ParsePrivateKeyBytes]. 91 + // 92 + // 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. 93 + func (k *PrivateKeyP256) Bytes() []byte { 94 + skEcdh, err := k.privP256.ECDH() 95 + if err != nil { 96 + panic("unexpected failure to export P-256 private key, after being exportable at parse time") 97 + } 98 + return skEcdh.Bytes() 99 + } 100 + 101 + // First hashes the raw bytes, then signs the digest, returning a binary signature. 102 + // 103 + // 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. 104 + // 105 + // 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. 106 + // 107 + // 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. 108 + func (k *PrivateKeyP256) HashAndSign(content []byte) ([]byte, error) { 109 + hash := sha256.Sum256(content) 110 + r, s, err := ecdsa.Sign(rand.Reader, k.privP256, hash[:]) 111 + if err != nil { 112 + return nil, fmt.Errorf("crypto error signing with P-256/secp256r1 private key: %w", err) 113 + } 114 + s = sigSToLowS_P256(s) 115 + sig := make([]byte, 64) 116 + r.FillBytes(sig[:32]) 117 + s.FillBytes(sig[32:]) 118 + return sig, nil 119 + } 120 + 121 + // Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.Bytes] method. This is the "compressed" curve format. 122 + // 123 + // 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. 124 + func ParsePublicBytesP256(data []byte) (*PublicKeyP256, error) { 125 + curve := elliptic.P256() 126 + x, y := elliptic.UnmarshalCompressed(curve, data) 127 + if x == nil { 128 + return nil, fmt.Errorf("invalid P-256 public key (x==nil)") 129 + } 130 + if !curve.Params().IsOnCurve(x, y) { 131 + return nil, fmt.Errorf("invalid P-256 public key (not on curve)") 132 + } 133 + pubK := &ecdsa.PublicKey{ 134 + Curve: curve, 135 + X: x, 136 + Y: y, 137 + } 138 + pub := PublicKeyP256{pubP256: pubK} 139 + err := pub.ensureBytes() 140 + if err != nil { 141 + return nil, err 142 + } 143 + return &pub, nil 144 + } 145 + 146 + // Loads a [PublicKey] of the indicated curve type from raw bytes, as exported by the [PublicKey.UncompressedBytes] method. 147 + // 148 + // 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. 149 + func ParsePublicUncompressedBytesP256(data []byte) (*PublicKeyP256, error) { 150 + curve := elliptic.P256() 151 + x, y := elliptic.Unmarshal(curve, data) 152 + if x == nil { 153 + return nil, fmt.Errorf("invalid P-256 public key (x==nil)") 154 + } 155 + if !curve.Params().IsOnCurve(x, y) { 156 + return nil, fmt.Errorf("invalid P-256 public key (not on curve)") 157 + } 158 + pubK := &ecdsa.PublicKey{ 159 + Curve: curve, 160 + X: x, 161 + Y: y, 162 + } 163 + pub := PublicKeyP256{pubP256: pubK} 164 + err := pub.ensureBytes() 165 + if err != nil { 166 + return nil, err 167 + } 168 + return &pub, nil 169 + } 170 + 171 + // Checks if the two public keys are the same. Note that the naive == operator does not work for most equality checks. 172 + func (k *PublicKeyP256) Equal(other PublicKey) bool { 173 + otherP256, ok := other.(*PublicKeyP256) 174 + if ok { 175 + return k.pubP256.Equal(otherP256.pubP256) 176 + } 177 + return false 178 + } 179 + 180 + // checks that key will be exportable later, both compressed and uncompressed 181 + func (k *PublicKeyP256) ensureBytes() error { 182 + if !k.pubP256.Curve.IsOnCurve(k.pubP256.X, k.pubP256.Y) { 183 + return fmt.Errorf("unexpected invalid P-256/secp256r1 public key (internal)") 184 + } 185 + _, err := k.pubP256.ECDH() 186 + return err 187 + } 188 + 189 + // Serializes the [PublicKey] in to "uncompressed" binary format. 190 + func (k *PublicKeyP256) UncompressedBytes() []byte { 191 + pkEcdh, err := k.pubP256.ECDH() 192 + if err != nil { 193 + panic("unexpected invalid P-256/secp256r1 public key, was verified at parse time") 194 + } 195 + return pkEcdh.Bytes() 196 + } 197 + 198 + // Serializes the [PublicKey] in to "compressed" binary format. 199 + func (k *PublicKeyP256) Bytes() []byte { 200 + return elliptic.MarshalCompressed(k.pubP256.Curve, k.pubP256.X, k.pubP256.Y) 201 + } 202 + 203 + // First hashes the raw bytes, then verifies the digest, returning `nil` for valid signatures, or an error for any failure. 204 + // 205 + // 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. 206 + // 207 + // Calling code is responsible for any string decoding of signatures (eg, hex or base64) before calling this function. 208 + // 209 + // This method requires a "low-S" signature, as specified by atproto. 210 + func (k *PublicKeyP256) HashAndVerify(content, sig []byte) error { 211 + hash := sha256.Sum256(content) 212 + // parseP256Sig 213 + if len(sig) != 64 { 214 + return fmt.Errorf("crypto: P-256 signatures must be 64 bytes, got len=%d", len(sig)) 215 + } 216 + r := big.NewInt(0) 217 + s := big.NewInt(0) 218 + r.SetBytes(sig[:32]) 219 + s.SetBytes(sig[32:]) 220 + 221 + if !ecdsa.Verify(k.pubP256, hash[:], r, s) { 222 + return fmt.Errorf("crypto: invalid signature") 223 + } 224 + 225 + // ensure that signature is low-S 226 + if !sigSIsLowS_P256(s) { 227 + return fmt.Errorf("crypto: invalid signature (high-S P-256)") 228 + } 229 + 230 + return nil 231 + } 232 + 233 + // Returns a multibased string encoding of the public key, including a multicodec indicator and compressed curve bytes serialization 234 + func (k *PublicKeyP256) Multibase() string { 235 + kbytes := k.Bytes() 236 + // multicodec p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24] 237 + kbytes = append([]byte{0x80, 0x24}, kbytes...) 238 + return "z" + base58.Encode(kbytes) 239 + } 240 + 241 + // Returns the DID cryptographic suite string which would be included in the `type` field of a `verificationMethod`. 242 + func (k *PublicKeyP256) LegacyDidDocSuite() string { 243 + return "EcdsaSecp256r1VerificationKey2019" 244 + } 245 + 246 + // Returns a did:key string encoding of the public key, as would be encoded in a DID PLC operation: 247 + // 248 + // - compressed / compacted binary representation 249 + // - prefix with appropriate curve multicodec bytes 250 + // - encode bytes with base58btc 251 + // - add "z" prefix to indicate encoding 252 + // - add "did:key:" prefix 253 + func (k *PublicKeyP256) DidKey() string { 254 + return "did:key:" + k.Multibase() 255 + } 256 + 257 + // Returns multibase string encoding of the public key, as would be included in an older DID Document "verificationMethod" section: 258 + // 259 + // - non-compressed / non-compacted binary representation 260 + // - encode bytes with base58btc 261 + // - prefix "z" (lower-case) to indicate encoding 262 + func (k *PublicKeyP256) LegacyMultibase() string { 263 + kbytes := k.UncompressedBytes() 264 + return "z" + base58.Encode(kbytes) 265 + }
+19 -8
atproto/crypto/w3c_didkey_test.go
··· 20 20 func TestDidKeyFixtures(t *testing.T) { 21 21 22 22 fixtureBatches := []struct { 23 - path string 24 - kt KeyType 23 + path string 24 + keyType string 25 25 }{ 26 - {path: "testdata/w3c_didkey_P256.json", kt: P256}, 27 - {path: "testdata/w3c_didkey_K256.json", kt: K256}, 26 + {path: "testdata/w3c_didkey_P256.json", keyType: "P256"}, 27 + {path: "testdata/w3c_didkey_K256.json", keyType: "K256"}, 28 28 } 29 29 30 30 for _, batch := range fixtureBatches { ··· 46 46 } 47 47 48 48 for _, row := range fixtures { 49 - testDidKeyFixture(t, row, batch.kt) 49 + testDidKeyFixture(t, row, batch.keyType) 50 50 } 51 51 } 52 52 } 53 53 54 - func testDidKeyFixture(t *testing.T, row DidKeyFixture, kt KeyType) { 54 + func testDidKeyFixture(t *testing.T, row DidKeyFixture, keyType string) { 55 55 assert := assert.New(t) 56 56 57 57 var raw []byte ··· 70 70 t.Fatal("no private key found") 71 71 } 72 72 73 - priv, err := ParsePrivateKeyBytes(raw, kt) 73 + var priv PrivateKey 74 + switch keyType { 75 + case "P256": 76 + priv, err = ParsePrivateBytesP256(raw) 77 + case "K256": 78 + priv, err = ParsePrivateBytesK256(raw) 79 + default: 80 + panic("impossible key type") 81 + } 74 82 if err != nil { 75 83 t.Fatal(err) 76 84 } 77 - kBytes := priv.Public() 85 + kBytes, err := priv.Public() 86 + if err != nil { 87 + t.Fatal(err) 88 + } 78 89 kDidKey, err := ParsePublicDidKey(row.PublicDidKey) 79 90 if err != nil { 80 91 t.Fatal(err)