x25519: use go's ecdh keys instead of a custom type

This commit is contained in:
Michael Muré
2025-05-08 08:23:47 +02:00
parent 1d61fcf2a0
commit 265c97f9f4
5 changed files with 70 additions and 212 deletions

View File

@@ -49,45 +49,40 @@ func Decode(identifier string) (did.DID, error) {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
d := DidKey{msi: msi}
switch code {
case ed25519.MultibaseCode:
d.signature, err = ed25519.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", msi, msi), bytes[read:], d)
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
xpub, err := x25519.PublicKeyFromEd25519(bytes[read:])
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
xmsi := x25519.PublicKeyToMultibase(xpub)
d.keyAgreement, err = x25519.NewKeyAgreementKey2020(fmt.Sprintf("did:key:%s#%s", msi, xmsi), xpub, d)
pub, err := ed25519.PublicKeyFromBytes(bytes[read:])
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
return FromPublicKey(pub)
// case P256: // TODO
// case Secp256k1: // TODO
// case RSA: // TODO
default:
return nil, fmt.Errorf("%w: unsupported did:key multicodec: 0x%x", did.ErrInvalidDid, code)
// case P256: // TODO
// case Secp256k1: // TODO
// case RSA: // TODO
}
return d, nil
return nil, fmt.Errorf("%w: unsupported did:key multicodec: 0x%x", did.ErrInvalidDid, code)
}
func FromPublicKey(pub PublicKey) (did.DID, error) {
var err error
switch pub := pub.(type) {
case ed25519.PublicKey:
d := DidKey{
msi: ed25519.PublicKeyToMultibase(pub),
}
d := DidKey{msi: ed25519.PublicKeyToMultibase(pub)}
d.signature, err = ed25519.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
if err != nil {
return nil, err
}
xpub, err := x25519.PublicKeyFromEd25519(pub)
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
xmsi := x25519.PublicKeyToMultibase(xpub)
d.keyAgreement, err = x25519.NewKeyAgreementKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, xmsi), xpub, d)
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
return d, nil
default:

View File

@@ -14,6 +14,7 @@ import (
const (
MultibaseCode = uint64(0xed)
JsonLdContext = "https://w3id.org/security/suites/ed25519-2020/v1"
Type = "Ed25519VerificationKey2020"
)
var _ did.VerificationMethodSignature = &VerificationKey2020{}
@@ -84,7 +85,7 @@ func (v VerificationKey2020) ID() string {
}
func (v VerificationKey2020) Type() string {
return "Ed25519VerificationKey2020"
return Type
}
func (v VerificationKey2020) Controller() string {

View File

@@ -22,7 +22,7 @@ func PublicKeyFromBytes(b []byte) (PublicKey, error) {
if len(b) != PublicKeySize {
return nil, fmt.Errorf("invalid ed25519 public key size")
}
return ed25519.PublicKey(b), nil
return PublicKey(b), nil
}
// PublicKeyFromMultibase decodes the public key from its Multibase form

View File

@@ -1,13 +1,11 @@
package x25519
import (
"crypto/ecdh"
"encoding/json"
"errors"
"fmt"
mbase "github.com/multiformats/go-multibase"
"github.com/multiformats/go-varint"
"github.com/INFURA/go-did"
)
@@ -16,6 +14,7 @@ import (
const (
MultibaseCode = uint64(0xec)
JsonLdContext = "https://w3id.org/security/suites/x25519-2020/v1"
Type = "X25519KeyAgreementKey2020"
)
var _ did.VerificationMethodKeyAgreement = &KeyAgreementKey2020{}
@@ -27,8 +26,8 @@ type KeyAgreementKey2020 struct {
}
func NewKeyAgreementKey2020(id string, pubkey PublicKey, controller did.DID) (*KeyAgreementKey2020, error) {
if len(pubkey) != PublicKeySize {
return nil, errors.New("invalid x25519 public key size")
if pubkey.Curve() != ecdh.X25519() {
return nil, errors.New("x25519 key curve must be X25519")
}
return &KeyAgreementKey2020{
@@ -70,7 +69,7 @@ func (k *KeyAgreementKey2020) UnmarshalJSON(bytes []byte) error {
if len(k.id) == 0 {
return errors.New("invalid id")
}
k.pubkey, err = MultibaseToPublicKey(aux.PublicKeyMultibase)
k.pubkey, err = PublicKeyFromMultibase(aux.PublicKeyMultibase)
if err != nil {
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
}
@@ -86,7 +85,7 @@ func (k KeyAgreementKey2020) ID() string {
}
func (k KeyAgreementKey2020) Type() string {
return "X25519KeyAgreementKey2020"
return Type
}
func (k KeyAgreementKey2020) Controller() string {
@@ -97,35 +96,7 @@ func (k KeyAgreementKey2020) JsonLdContext() string {
return JsonLdContext
}
// PublicKeyToMultibase encodes the public key in a suitable way for publicKeyMultibase
func PublicKeyToMultibase(pub PublicKey) string {
// can only fail with an invalid encoding, but it's hardcoded
bytes, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(MultibaseCode), pub...))
return bytes
}
// MultibaseToPublicKey decodes the public key from its publicKeyMultibase form
func MultibaseToPublicKey(multibase string) (PublicKey, error) {
baseCodec, bytes, err := mbase.Decode(multibase)
if err != nil {
return nil, err
}
// the specification enforces that encoding
if baseCodec != mbase.Base58BTC {
return nil, fmt.Errorf("not Base58BTC encoded")
}
code, read, err := varint.FromUvarint(bytes)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
if read != 2 {
return nil, fmt.Errorf("unexpected multibase")
}
if len(bytes)-read != PublicKeySize {
return nil, fmt.Errorf("invalid ed25519 public key size")
}
return bytes[read:], nil
// TODO: make it part of did.VerificationMethodKeyAgreement in some way
func (k KeyAgreementKey2020) KeyAgreement(priv PrivateKey) ([]byte, error) {
return priv.ECDH(k.pubkey)
}

View File

@@ -1,171 +1,32 @@
package x25519
// TODO: use ecdh.PublicKey instead of defining a custom type below?
// type PublicKey ecdh.PublicKey
//
// func (p PublicKey) Equal(x crypto.PublicKey) bool {
// // TODO implement me
// panic("implement me")
// }
//
// type PrivateKey ecdh.PrivateKey
//
// func (p *PrivateKey) Public() crypto.PublicKey {
// key := p.(ecdh.PrivateKey)
// return key.Public()
// }
//
// func (p *PrivateKey) Equal(x crypto.PrivateKey) bool {
// // TODO implement me
// panic("implement me")
// }
//
// func GenerateKeyPair() (PublicKey, PrivateKey, error) {
// priv, err := ecdh.X25519().GenerateKey(rand.Reader)
// if err != nil {
// return nil, nil, err
// }
// return priv.Public().(PublicKey), priv, nil
// }
//
// // PublicKeyToMultibase encodes the public key in a suitable way for publicKeyMultibase
// func PublicKeyToMultibase(pub PublicKey) string {
// // can only fail with an invalid encoding, but it's hardcoded
// bytes, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(MultibaseCode), pub.Bytes()...))
// return bytes
// }
//
// // MultibaseToPublicKey decodes the public key from its publicKeyMultibase form
// func MultibaseToPublicKey(multibase string) (PublicKey, error) {
// baseCodec, bytes, err := mbase.Decode(multibase)
// if err != nil {
// return nil, err
// }
// // the specification enforces that encoding
// if baseCodec != mbase.Base58BTC {
// return nil, fmt.Errorf("not Base58BTC encoded")
// }
// code, read, err := varint.FromUvarint(bytes)
// if err != nil {
// return nil, err
// }
// if code != MultibaseCode {
// return nil, fmt.Errorf("invalid code")
// }
// if read != 2 {
// return nil, fmt.Errorf("unexpected multibase")
// }
// if len(bytes)-read != ed25519.PublicKeySize {
// return nil, fmt.Errorf("invalid ed25519 public key size")
// }
// return bytes[read:], nil
// }
import (
"bytes"
"crypto"
"crypto/ecdh"
"crypto/rand"
"fmt"
"io"
"math/big"
"golang.org/x/crypto/curve25519"
mbase "github.com/multiformats/go-multibase"
"github.com/multiformats/go-varint"
"github.com/INFURA/go-did/verifications/ed25519"
)
// This mirrors ed25519's structure for private/public "keys". We
// require dedicated types for these as they drive
// serialization/deserialization logic, as well as encryption types.
//
// Note that with the x25519 scheme, the private key is a sequence of
// 32 bytes, while the public key is the result of X25519(private,
// basepoint).
//
// Portions of this file are from Go's ed25519.go, which is
// Copyright 2016 The Go Authors. All rights reserved.
type PublicKey = *ecdh.PublicKey
type PrivateKey = *ecdh.PrivateKey
// Originally taken from github.com/lestrrat-go/jwx/v2/x25519.
const PublicKeySize = 32
const (
// PublicKeySize is the size, in bytes, of public keys as used in this package.
PublicKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
SeedSize = 32
)
// PublicKey is the type of X25519 public keys
type PublicKey []byte
// NewKeyFromSeed calculates a private key from a seed. It will return
// an error if len(seed) is not SeedSize. This function is provided
// for interoperability with RFC 7748. RFC 7748's private keys
// correspond to seeds in this package.
func NewKeyFromSeed(seed []byte) (PrivateKey, error) {
privateKey := make([]byte, PrivateKeySize)
if len(seed) != SeedSize {
return nil, fmt.Errorf("unexpected seed size: %d", len(seed))
}
copy(privateKey, seed)
public, err := curve25519.X25519(seed, curve25519.Basepoint)
if err != nil {
return nil, fmt.Errorf(`failed to compute public key: %w`, err)
}
copy(privateKey[SeedSize:], public)
return privateKey, nil
}
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey() (PublicKey, PrivateKey, error) {
seed := make([]byte, SeedSize)
if _, err := io.ReadFull(rand.Reader, seed); err != nil {
return nil, nil, err
}
privateKey, err := NewKeyFromSeed(seed)
func GenerateKeyPair() (PublicKey, PrivateKey, error) {
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
publicKey := make([]byte, PublicKeySize)
copy(publicKey, privateKey[SeedSize:])
return publicKey, privateKey, nil
return priv.Public().(PublicKey), priv, nil
}
// Any methods implemented on PublicKey might need to also be implemented on
// PrivateKey, as the latter embeds the former and will expose its methods.
// Equal reports whether pub and x have the same value.
func (pub PublicKey) Equal(x crypto.PublicKey) bool {
xx, ok := x.(PublicKey)
if !ok {
return false
}
return bytes.Equal(pub, xx)
}
// PrivateKey is the type of X25519 private key
type PrivateKey []byte
// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
publicKey := make([]byte, PublicKeySize)
copy(publicKey, priv[SeedSize:])
return PublicKey(publicKey)
}
// Equal reports whether priv and x have the same value.
func (priv PrivateKey) Equal(x crypto.PrivateKey) bool {
xx, ok := x.(PrivateKey)
if !ok {
return false
}
return bytes.Equal(priv, xx)
func PublicKeyFromBytes(b []byte) (PublicKey, error) {
return ecdh.X25519().NewPublicKey(b)
}
func PublicKeyFromEd25519(pub ed25519.PublicKey) (PublicKey, error) {
@@ -221,7 +82,37 @@ func PublicKeyFromEd25519(pub ed25519.PublicKey) (PublicKey, error) {
// x25519 are little-endian, but big.Int give us big-endian.
// See https://www.ietf.org/rfc/rfc7748.txt
return reverseBytes(res), nil
return ecdh.X25519().NewPublicKey(reverseBytes(res))
}
// PublicKeyFromMultibase decodes the public key from its Multibase form
func PublicKeyFromMultibase(multibase string) (PublicKey, error) {
baseCodec, bytes, err := mbase.Decode(multibase)
if err != nil {
return nil, err
}
// the specification enforces that encoding
if baseCodec != mbase.Base58BTC {
return nil, fmt.Errorf("not Base58BTC encoded")
}
code, read, err := varint.FromUvarint(bytes)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
if read != 2 {
return nil, fmt.Errorf("unexpected multibase")
}
return ecdh.X25519().NewPublicKey(bytes[read:])
}
// PublicKeyToMultibase encodes the public key in a suitable way for publicKeyMultibase
func PublicKeyToMultibase(pub PublicKey) string {
// can only fail with an invalid encoding, but it's hardcoded
bytes, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(MultibaseCode), pub.Bytes()...))
return bytes
}
func reverseBytes(b []byte) []byte {