Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: support for multibase (de)ser of private keys #322

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions atproto/crypto/k256.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ func (k PrivateKeyK256) Bytes() []byte {
return k.privK256.Bytes()
}

// Multibase string encoding of the private key, including a multicodec indicator
func (k *PrivateKeyK256) Multibase() string {
kbytes := k.Bytes()
// multicodec secp256k1-priv, code 0x1301, varint-encoded bytes: [0x81, 0x26]
kbytes = append([]byte{0x81, 0x26}, kbytes...)
return "z" + base58.Encode(kbytes)
}

// Outputs the [PublicKey] corresponding to this [PrivateKeyK256]; it will be a [PublicKeyK256].
func (k PrivateKeyK256) PublicKey() (PublicKey, error) {
pub := PublicKeyK256{pubK256: k.privK256.PublicKey()}
Expand Down
36 changes: 36 additions & 0 deletions atproto/crypto/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,42 @@ type PublicKey interface {

var ErrInvalidSignature = errors.New("crytographic signature invalid")

/*
// quick code to verify varint byte conversion (https://play.golang.com/):
import (
"encoding/binary"
"fmt"
)
buf := make([]byte, binary.MaxVarintLen64)
for _, x := range []uint64{0xE7, 0x1200, 0x1306, 0x1301} {
n := binary.PutUvarint(buf, x)
fmt.Printf("%x -> %x\n", x, buf[:n])
}
*/

// Loads a private key from multibase string encoding, with multicodec indicating the key type.
func ParsePrivateMultibase(encoded string) (PrivateKeyExportable, error) {
if len(encoded) < 2 || encoded[0] != 'z' {
return nil, fmt.Errorf("crypto: not a multibase base58btc string")
}
data, err := base58.Decode(encoded[1:])
if err != nil {
return nil, fmt.Errorf("crypto: not a multibase base58btc string")
}
if len(data) < 3 {
return nil, fmt.Errorf("crypto: multibase key was too short")
}
if data[0] == 0x86 && data[1] == 0x26 {
// multicodec p256-priv, code 0x1306, varint-encoded bytes: [0x86, 0x26]
return ParsePrivateBytesP256(data[2:])
} else if data[0] == 0x81 && data[1] == 0x26 {
// multicodec secp256k1-priv, code 0x1301, varint-encoded bytes: [0x81, 0x26]
return ParsePrivateBytesK256(data[2:])
} else {
return nil, fmt.Errorf("unsupported atproto key type (unknown multicodec prefix)")
}
}

// Loads a public key from multibase string encoding, with multicodec indicating the key type.
func ParsePublicMultibase(encoded string) (PublicKey, error) {
if len(encoded) < 2 || encoded[0] != 'z' {
Expand Down
26 changes: 26 additions & 0 deletions atproto/crypto/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func TestKeyBasics(t *testing.T) {
privP256FromBytes, err := ParsePrivateBytesP256(privP256Bytes)
assert.NoError(err)
assert.True(privP256.Equal(privP256FromBytes))
privP256MB := privP256.Multibase()
privP256FromMB, err := ParsePrivateMultibase(privP256MB)
assert.NoError(err)
assert.True(privP256.Equal(privP256FromMB))

// private key generation and byte serialization (K-256)
privK256, err := GeneratePrivateKeyK256()
Expand All @@ -34,6 +38,10 @@ func TestKeyBasics(t *testing.T) {
privK256FromBytes, err := ParsePrivateBytesK256(privK256Bytes)
assert.NoError(err)
assert.Equal(privK256, privK256FromBytes)
privK256MB := privK256.Multibase()
privK256FromMB, err := ParsePrivateMultibase(privK256MB)
assert.NoError(err)
assert.True(privK256.Equal(privK256FromMB))

// public key byte serialization (P-256)
pubP256, err := privP256.PublicKey()
Expand Down Expand Up @@ -146,3 +154,21 @@ func TestKeyCompressionK256(t *testing.T) {
assert.Equal(65, len(pub.UncompressedBytes()))
assert.Equal(64, len(sig))
}

func TestParsePrivateMultibase(t *testing.T) {
assert := assert.New(t)

// these values generated by this library, not an external source
privP256MB := "z42tvqQS5sVhaV1jLZ5P6ZKEPEbSpYavNVmT88YDYV3MEZ8D"
privK256MB := "z3vLWgA9nXoPzxsJJafDY9BPrZd3EDWjvcCtYfrFxZ7xbMVi"

privP256FromMB, err := ParsePrivateMultibase(privP256MB)
assert.NoError(err)
_, ok := privP256FromMB.(*PrivateKeyP256)
assert.True(ok)

privK256FromMB, err := ParsePrivateMultibase(privK256MB)
assert.NoError(err)
_, ok = privK256FromMB.(*PrivateKeyK256)
assert.True(ok)
}
8 changes: 8 additions & 0 deletions atproto/crypto/p256.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ func (k *PrivateKeyP256) Bytes() []byte {
return k.privP256ecdh.Bytes()
}

// Multibase string encoding of the private key, including a multicodec indicator
func (k *PrivateKeyP256) Multibase() string {
kbytes := k.Bytes()
// multicodec p256-priv, code 0x1306, varint-encoded bytes: [0x86, 0x26]
kbytes = append([]byte{0x86, 0x26}, kbytes...)
return "z" + base58.Encode(kbytes)
}

// Outputs the [PublicKey] corresponding to this [PrivateKeyP256]; it will be a [PublicKeyP256].
func (k *PrivateKeyP256) PublicKey() (PublicKey, error) {
pkECDSA, ok := k.privP256.Public().(*ecdsa.PublicKey)
Expand Down