cli: change the module name of the cli (#1431)
This commit is contained in:
108
cli/pkg/web5/crypto/dsa/dsa.go
Normal file
108
cli/pkg/web5/crypto/dsa/dsa.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package dsa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"olares-cli/pkg/web5/crypto/dsa/ecdsa"
|
||||
"olares-cli/pkg/web5/crypto/dsa/eddsa"
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
)
|
||||
|
||||
const (
|
||||
AlgorithmIDSECP256K1 = ecdsa.SECP256K1AlgorithmID
|
||||
AlgorithmIDED25519 = eddsa.ED25519AlgorithmID
|
||||
)
|
||||
|
||||
// GeneratePrivateKey generates a private key using the algorithm specified by algorithmID.
|
||||
func GeneratePrivateKey(algorithmID string) (jwk.JWK, error) {
|
||||
if ecdsa.SupportsAlgorithmID(algorithmID) {
|
||||
return ecdsa.GeneratePrivateKey(algorithmID)
|
||||
} else if eddsa.SupportsAlgorithmID(algorithmID) {
|
||||
return eddsa.GeneratePrivateKey(algorithmID)
|
||||
}
|
||||
|
||||
return jwk.JWK{}, fmt.Errorf("unsupported algorithm: %s", algorithmID)
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key corresponding to the given private key.
|
||||
func GetPublicKey(privateKey jwk.JWK) jwk.JWK {
|
||||
switch privateKey.KTY {
|
||||
case ecdsa.KeyType:
|
||||
return ecdsa.GetPublicKey(privateKey)
|
||||
case eddsa.KeyType:
|
||||
return eddsa.GetPublicKey(privateKey)
|
||||
default:
|
||||
return jwk.JWK{}
|
||||
}
|
||||
}
|
||||
|
||||
// Sign signs the payload using the given private key.
|
||||
func Sign(payload []byte, jwk jwk.JWK) ([]byte, error) {
|
||||
switch jwk.KTY {
|
||||
case ecdsa.KeyType:
|
||||
return ecdsa.Sign(payload, jwk)
|
||||
case eddsa.KeyType:
|
||||
return eddsa.Sign(payload, jwk)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type: %s", jwk.KTY)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify verifies the signature of the payload using the given public key.
|
||||
func Verify(payload []byte, signature []byte, jwk jwk.JWK) (bool, error) {
|
||||
switch jwk.KTY {
|
||||
case ecdsa.KeyType:
|
||||
return ecdsa.Verify(payload, signature, jwk)
|
||||
case eddsa.KeyType:
|
||||
return eddsa.Verify(payload, signature, jwk)
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported key type: %s", jwk.KTY)
|
||||
}
|
||||
}
|
||||
|
||||
// GetJWA returns the JWA (JSON Web Algorithm) algorithm corresponding to the given key.
|
||||
func GetJWA(jwk jwk.JWK) (string, error) {
|
||||
switch jwk.KTY {
|
||||
case ecdsa.KeyType:
|
||||
return ecdsa.GetJWA(jwk)
|
||||
case eddsa.KeyType:
|
||||
return eddsa.GetJWA(jwk)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported key type: %s", jwk.KTY)
|
||||
}
|
||||
}
|
||||
|
||||
// BytesToPublicKey converts the given bytes to a public key based on the algorithm specified by algorithmID.
|
||||
func BytesToPublicKey(algorithmID string, input []byte) (jwk.JWK, error) {
|
||||
if ecdsa.SupportsAlgorithmID(algorithmID) {
|
||||
return ecdsa.BytesToPublicKey(algorithmID, input)
|
||||
} else if eddsa.SupportsAlgorithmID(algorithmID) {
|
||||
return eddsa.BytesToPublicKey(algorithmID, input)
|
||||
}
|
||||
|
||||
return jwk.JWK{}, fmt.Errorf("unsupported algorithm: %s", algorithmID)
|
||||
}
|
||||
|
||||
// PublicKeyToBytes converts the provided public key to bytes
|
||||
func PublicKeyToBytes(publicKey jwk.JWK) ([]byte, error) {
|
||||
switch publicKey.KTY {
|
||||
case ecdsa.KeyType:
|
||||
return ecdsa.PublicKeyToBytes(publicKey)
|
||||
case eddsa.KeyType:
|
||||
return eddsa.PublicKeyToBytes(publicKey)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type: %s", publicKey.KTY)
|
||||
}
|
||||
}
|
||||
|
||||
// AlgorithmID returns the algorithm ID for the given jwk.JWK
|
||||
func AlgorithmID(jwk *jwk.JWK) (string, error) {
|
||||
switch jwk.KTY {
|
||||
case ecdsa.KeyType:
|
||||
return ecdsa.AlgorithmID(jwk)
|
||||
case eddsa.KeyType:
|
||||
return eddsa.AlgorithmID(jwk)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported key type: %s", jwk.KTY)
|
||||
}
|
||||
}
|
||||
150
cli/pkg/web5/crypto/dsa/dsa_test.go
Normal file
150
cli/pkg/web5/crypto/dsa/dsa_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package dsa_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"olares-cli/pkg/web5/crypto/dsa"
|
||||
"olares-cli/pkg/web5/crypto/dsa/ecdsa"
|
||||
"olares-cli/pkg/web5/crypto/dsa/eddsa"
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestGeneratePrivateKeySECP256K1(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDSECP256K1)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal[string](t, ecdsa.SECP256K1JWACurve, privateJwk.CRV)
|
||||
assert.Equal[string](t, ecdsa.KeyType, privateJwk.KTY)
|
||||
assert.True(t, privateJwk.D != "", "privateJwk.D is empty")
|
||||
assert.True(t, privateJwk.X != "", "privateJwk.X is empty")
|
||||
assert.True(t, privateJwk.Y != "", "privateJwk.Y is empty")
|
||||
}
|
||||
|
||||
func TestGeneratePrivateKeyED25519(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDED25519)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate private key: %v", err.Error())
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal[string](t, eddsa.ED25519JWACurve, privateJwk.CRV)
|
||||
assert.Equal[string](t, eddsa.KeyType, privateJwk.KTY)
|
||||
assert.True(t, privateJwk.D != "", "privateJwk.D is empty")
|
||||
assert.True(t, privateJwk.X != "", "privateJwk.X is empty")
|
||||
}
|
||||
|
||||
func TestSignSECP256K1(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDSECP256K1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
payload := []byte("hello world")
|
||||
signature, err := dsa.Sign(payload, privateJwk)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(signature) == 64, "invalid signature length")
|
||||
}
|
||||
|
||||
func TestSignED25519(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDED25519)
|
||||
assert.NoError(t, err)
|
||||
|
||||
payload := []byte("hello world")
|
||||
signature, err := dsa.Sign(payload, privateJwk)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(signature) == 64, "invalid signature length")
|
||||
}
|
||||
|
||||
func TestSignDeterministicSECP256K1(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDSECP256K1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
payload := []byte("hello world")
|
||||
signature1, err := dsa.Sign(payload, privateJwk)
|
||||
assert.NoError(t, err, "failed to sign")
|
||||
|
||||
signature2, err := dsa.Sign(payload, privateJwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, signature1, signature2, "signature is not deterministic")
|
||||
}
|
||||
|
||||
func TestSignDeterministicED25519(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDED25519)
|
||||
assert.NoError(t, err)
|
||||
|
||||
payload := []byte("hello world")
|
||||
signature1, err := dsa.Sign(payload, privateJwk)
|
||||
assert.NoError(t, err, "failed to sign")
|
||||
|
||||
signature2, err := dsa.Sign(payload, privateJwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, signature1, signature2, "signature is not deterministic")
|
||||
}
|
||||
|
||||
func TestVerifySECP256K1(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDSECP256K1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
payload := []byte("hello world")
|
||||
signature, err := dsa.Sign(payload, privateJwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
publicJwk := dsa.GetPublicKey(privateJwk)
|
||||
|
||||
legit, err := dsa.Verify(payload, signature, publicJwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.True(t, legit, "failed to verify signature")
|
||||
}
|
||||
|
||||
func TestVerifyED25519(t *testing.T) {
|
||||
privateJwk, err := dsa.GeneratePrivateKey(dsa.AlgorithmIDED25519)
|
||||
assert.NoError(t, err)
|
||||
|
||||
payload := []byte("hello world")
|
||||
signature, err := dsa.Sign(payload, privateJwk)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
publicJwk := dsa.GetPublicKey(privateJwk)
|
||||
|
||||
legit, err := dsa.Verify(payload, signature, publicJwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.True(t, legit, "failed to verify signature")
|
||||
}
|
||||
|
||||
func TestBytesToPublicKey_BadAlgorithm(t *testing.T) {
|
||||
_, err := dsa.BytesToPublicKey("yolocrypto", []byte{0x00, 0x01, 0x02, 0x03})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBytesToPublicKey_BadBytes(t *testing.T) {
|
||||
_, err := dsa.BytesToPublicKey(dsa.AlgorithmIDSECP256K1, []byte{0x00, 0x01, 0x02, 0x03})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBytesToPublicKey_SECP256K1(t *testing.T) {
|
||||
// vector taken from https://github.com/decentralized-identity/web5-js/blob/dids-new-crypto/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json
|
||||
publicKeyHex := "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
|
||||
pubKeyBytes, err := hex.DecodeString(publicKeyHex)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jwk, err := dsa.BytesToPublicKey(dsa.AlgorithmIDSECP256K1, pubKeyBytes)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, ecdsa.SECP256K1JWACurve, jwk.CRV)
|
||||
assert.Equal(t, ecdsa.KeyType, jwk.KTY)
|
||||
assert.Equal(t, "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", jwk.X)
|
||||
assert.Equal(t, "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg", jwk.Y)
|
||||
}
|
||||
|
||||
func TestPublicKeyToBytes_UnsupportedKTY(t *testing.T) {
|
||||
_, err := dsa.PublicKeyToBytes(jwk.JWK{KTY: "yolocrypto"})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
115
cli/pkg/web5/crypto/dsa/ecdsa/ecdsa.go
Normal file
115
cli/pkg/web5/crypto/dsa/ecdsa/ecdsa.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package ecdsa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyType = "EC"
|
||||
)
|
||||
|
||||
var algorithmIDs = map[string]bool{
|
||||
SECP256K1AlgorithmID: true,
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates an ECDSA private key for the given algorithm
|
||||
func GeneratePrivateKey(algorithmID string) (jwk.JWK, error) {
|
||||
switch algorithmID {
|
||||
case SECP256K1AlgorithmID:
|
||||
return SECP256K1GeneratePrivateKey()
|
||||
default:
|
||||
return jwk.JWK{}, fmt.Errorf("unsupported algorithm: %s", algorithmID)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPublicKey builds an ECDSA public key from the given ECDSA private key
|
||||
func GetPublicKey(privateKey jwk.JWK) jwk.JWK {
|
||||
return jwk.JWK{
|
||||
KTY: privateKey.KTY,
|
||||
CRV: privateKey.CRV,
|
||||
X: privateKey.X,
|
||||
Y: privateKey.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Sign generates a cryptographic signature for the given payload with the given private key
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The function will automatically detect the given ECDSA cryptographic curve from the given private key
|
||||
func Sign(payload []byte, privateKey jwk.JWK) ([]byte, error) {
|
||||
if privateKey.D == "" {
|
||||
return nil, errors.New("d must be set")
|
||||
}
|
||||
|
||||
switch privateKey.CRV {
|
||||
case SECP256K1JWACurve:
|
||||
return SECP256K1Sign(payload, privateKey)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported curve: %s", privateKey.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify verifies the given signature over a given payload by the given public key
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The function will automatically detect the given ECDSA cryptographic curve from the given public key
|
||||
func Verify(payload []byte, signature []byte, publicKey jwk.JWK) (bool, error) {
|
||||
switch publicKey.CRV {
|
||||
case SECP256K1JWACurve:
|
||||
return SECP256K1Verify(payload, signature, publicKey)
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported curve: %s", publicKey.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// GetJWA returns the [JWA] for the given ECDSA key
|
||||
//
|
||||
// [JWA]: https://datatracker.ietf.org/doc/html/rfc7518
|
||||
func GetJWA(jwk jwk.JWK) (string, error) {
|
||||
switch jwk.CRV {
|
||||
case SECP256K1JWACurve:
|
||||
return SECP256K1JWA, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported curve: %s", jwk.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// BytesToPublicKey deserializes the given byte array into a jwk.JWK for the given cryptographic algorithm
|
||||
func BytesToPublicKey(algorithmID string, input []byte) (jwk.JWK, error) {
|
||||
switch algorithmID {
|
||||
case SECP256K1AlgorithmID:
|
||||
return SECP256K1BytesToPublicKey(input)
|
||||
default:
|
||||
return jwk.JWK{}, fmt.Errorf("unsupported algorithm: %s", algorithmID)
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeyToBytes serializes the given public key into a byte array
|
||||
func PublicKeyToBytes(publicKey jwk.JWK) ([]byte, error) {
|
||||
switch publicKey.CRV {
|
||||
case SECP256K1JWACurve:
|
||||
return SECP256K1PublicKeyToBytes(publicKey)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported curve: %s", publicKey.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsAlgorithmID informs as to whether or not the given algorithm ID is supported by this package
|
||||
func SupportsAlgorithmID(id string) bool {
|
||||
return algorithmIDs[id]
|
||||
}
|
||||
|
||||
// AlgorithmID returns the algorithm ID for the given jwk.JWK
|
||||
func AlgorithmID(jwk *jwk.JWK) (string, error) {
|
||||
switch jwk.CRV {
|
||||
case SECP256K1JWACurve:
|
||||
return SECP256K1AlgorithmID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported curve: %s", jwk.CRV)
|
||||
}
|
||||
}
|
||||
151
cli/pkg/web5/crypto/dsa/ecdsa/secp256k1.go
Normal file
151
cli/pkg/web5/crypto/dsa/ecdsa/secp256k1.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package ecdsa
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
|
||||
_secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
|
||||
)
|
||||
|
||||
const (
|
||||
SECP256K1JWA string = "ES256K"
|
||||
SECP256K1JWACurve string = "secp256k1"
|
||||
SECP256K1AlgorithmID string = SECP256K1JWACurve
|
||||
)
|
||||
|
||||
// SECP256K1GeneratePrivateKey generates a new private key
|
||||
func SECP256K1GeneratePrivateKey() (jwk.JWK, error) {
|
||||
keyPair, err := _secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return jwk.JWK{}, fmt.Errorf("failed to generate private key: %w", err)
|
||||
}
|
||||
|
||||
dBytes := keyPair.Key.Bytes()
|
||||
pubKey := keyPair.PubKey()
|
||||
xBytes := pubKey.X().Bytes()
|
||||
yBytes := pubKey.Y().Bytes()
|
||||
|
||||
privateKey := jwk.JWK{
|
||||
KTY: KeyType,
|
||||
CRV: SECP256K1JWACurve,
|
||||
D: base64.RawURLEncoding.EncodeToString(dBytes[:]),
|
||||
X: base64.RawURLEncoding.EncodeToString(xBytes),
|
||||
Y: base64.RawURLEncoding.EncodeToString(yBytes),
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// SECP256K1Sign signs the given payload with the given private key
|
||||
func SECP256K1Sign(payload []byte, privateKey jwk.JWK) ([]byte, error) {
|
||||
privateKeyBytes, err := base64.RawURLEncoding.DecodeString(privateKey.D)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode d %w", err)
|
||||
}
|
||||
|
||||
key := _secp256k1.PrivKeyFromBytes(privateKeyBytes)
|
||||
|
||||
hash := sha256.Sum256(payload)
|
||||
signature := ecdsa.SignCompact(key, hash[:], false)[1:]
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// SECP256K1Verify verifies the given signature over the given payload with the given public key
|
||||
func SECP256K1Verify(payload []byte, signature []byte, publicKey jwk.JWK) (bool, error) {
|
||||
if publicKey.X == "" || publicKey.Y == "" {
|
||||
return false, errors.New("x and y must be set")
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(payload)
|
||||
|
||||
keyBytes, err := secp256k1PublicKeyToUncheckedBytes(publicKey)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to convert public key to bytes: %w", err)
|
||||
}
|
||||
|
||||
key, err := _secp256k1.ParsePubKey(keyBytes)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
if len(signature) != 64 {
|
||||
return false, errors.New("signature must be 64 bytes")
|
||||
}
|
||||
|
||||
r := new(_secp256k1.ModNScalar)
|
||||
r.SetByteSlice(signature[:32])
|
||||
|
||||
s := new(_secp256k1.ModNScalar)
|
||||
s.SetByteSlice(signature[32:])
|
||||
|
||||
sig := ecdsa.NewSignature(r, s)
|
||||
legit := sig.Verify(hash[:], key)
|
||||
|
||||
return legit, nil
|
||||
}
|
||||
|
||||
// SECP256K1BytesToPublicKey converts a secp256k1 public key to a JWK.
|
||||
// Supports both Compressed and Uncompressed public keys described in
|
||||
// https://www.secg.org/sec1-v2.pdf section 2.3.3
|
||||
func SECP256K1BytesToPublicKey(input []byte) (jwk.JWK, error) {
|
||||
pubKey, err := _secp256k1.ParsePubKey(input)
|
||||
if err != nil {
|
||||
return jwk.JWK{}, fmt.Errorf("failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
return jwk.JWK{
|
||||
KTY: KeyType,
|
||||
CRV: SECP256K1JWACurve,
|
||||
X: base64.RawURLEncoding.EncodeToString(pubKey.X().Bytes()),
|
||||
Y: base64.RawURLEncoding.EncodeToString(pubKey.Y().Bytes()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SECP256K1PublicKeyToBytes converts a secp256k1 public key JWK to bytes.
|
||||
// Note: this function returns the uncompressed public key. compressed is not
|
||||
// yet supported
|
||||
func SECP256K1PublicKeyToBytes(publicKey jwk.JWK) ([]byte, error) {
|
||||
uncheckedBytes, err := secp256k1PublicKeyToUncheckedBytes(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := _secp256k1.ParsePubKey(uncheckedBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key: %w", err)
|
||||
}
|
||||
|
||||
return key.SerializeUncompressed(), nil
|
||||
}
|
||||
|
||||
func secp256k1PublicKeyToUncheckedBytes(publicKey jwk.JWK) ([]byte, error) {
|
||||
if publicKey.X == "" || publicKey.Y == "" {
|
||||
return nil, errors.New("x and y must be set")
|
||||
}
|
||||
|
||||
x, err := base64.RawURLEncoding.DecodeString(publicKey.X)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode x: %w", err)
|
||||
}
|
||||
|
||||
y, err := base64.RawURLEncoding.DecodeString(publicKey.Y)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode y: %w", err)
|
||||
}
|
||||
|
||||
// Prepend 0x04 to indicate an uncompressed public key format for secp256k1.
|
||||
// This byte is a prefix that distinguishes uncompressed keys, which include both X and Y coordinates,
|
||||
// from compressed keys which only include one coordinate and an indication of the other's parity.
|
||||
// The secp256k1 standard requires this prefix for uncompressed keys to ensure proper interpretation.
|
||||
keyBytes := []byte{0x04}
|
||||
keyBytes = append(keyBytes, x...)
|
||||
keyBytes = append(keyBytes, y...)
|
||||
|
||||
return keyBytes, nil
|
||||
}
|
||||
99
cli/pkg/web5/crypto/dsa/ecdsa/secp256k1_test.go
Normal file
99
cli/pkg/web5/crypto/dsa/ecdsa/secp256k1_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package ecdsa_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"olares-cli/pkg/web5/crypto/dsa/ecdsa"
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestSECP256K1GeneratePrivateKey(t *testing.T) {
|
||||
key, err := ecdsa.SECP256K1GeneratePrivateKey()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, ecdsa.KeyType, key.KTY)
|
||||
assert.Equal(t, ecdsa.SECP256K1JWACurve, key.CRV)
|
||||
assert.True(t, key.D != "", "privateJwk.D is empty")
|
||||
assert.True(t, key.X != "", "privateJwk.X is empty")
|
||||
assert.True(t, key.Y != "", "privateJwk.Y is empty")
|
||||
}
|
||||
|
||||
func TestSECP256K1BytesToPublicKey_Bad(t *testing.T) {
|
||||
_, err := ecdsa.SECP256K1BytesToPublicKey([]byte{0x00, 0x01, 0x02, 0x03})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSECP256K1BytesToPublicKey_Uncompressed(t *testing.T) {
|
||||
// vector taken from https://github.com/decentralized-identity/web5-js/blob/dids-new-crypto/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json
|
||||
publicKeyHex := "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
|
||||
pubKeyBytes, err := hex.DecodeString(publicKeyHex)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jwk, err := ecdsa.SECP256K1BytesToPublicKey(pubKeyBytes)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, ecdsa.SECP256K1JWACurve, jwk.CRV)
|
||||
assert.Equal(t, ecdsa.KeyType, jwk.KTY)
|
||||
assert.Equal(t, "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", jwk.X)
|
||||
assert.Equal(t, "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg", jwk.Y)
|
||||
}
|
||||
|
||||
func TestSECP256K1PublicKeyToBytes(t *testing.T) {
|
||||
// vector taken from https://github.com/decentralized-identity/web5-js/blob/dids-new-crypto/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json
|
||||
jwk := jwk.JWK{
|
||||
KTY: "EC",
|
||||
CRV: ecdsa.SECP256K1JWACurve,
|
||||
X: "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g",
|
||||
Y: "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg",
|
||||
}
|
||||
|
||||
pubKeyBytes, err := ecdsa.SECP256K1PublicKeyToBytes(jwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pubKeyHex := hex.EncodeToString(pubKeyBytes)
|
||||
expected := "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
|
||||
|
||||
assert.Equal(t, expected, pubKeyHex)
|
||||
}
|
||||
|
||||
func TestSECP256K1PublicKeyToBytes_Bad(t *testing.T) {
|
||||
vectors := []jwk.JWK{
|
||||
{
|
||||
KTY: "EC",
|
||||
CRV: ecdsa.SECP256K1JWACurve,
|
||||
X: "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g",
|
||||
},
|
||||
{
|
||||
KTY: "EC",
|
||||
CRV: ecdsa.SECP256K1JWACurve,
|
||||
Y: "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g",
|
||||
},
|
||||
{
|
||||
KTY: "EC",
|
||||
CRV: ecdsa.SECP256K1JWACurve,
|
||||
X: "=///",
|
||||
Y: "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg",
|
||||
},
|
||||
{
|
||||
KTY: "EC",
|
||||
CRV: ecdsa.SECP256K1JWACurve,
|
||||
X: "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g",
|
||||
Y: "=///",
|
||||
},
|
||||
{
|
||||
KTY: "EC",
|
||||
CRV: ecdsa.SECP256K1JWACurve,
|
||||
X: "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g",
|
||||
Y: "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, vec := range vectors {
|
||||
pubKeyBytes, err := ecdsa.SECP256K1PublicKeyToBytes(vec)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, nil, pubKeyBytes)
|
||||
}
|
||||
}
|
||||
82
cli/pkg/web5/crypto/dsa/eddsa/ed25519.go
Normal file
82
cli/pkg/web5/crypto/dsa/eddsa/ed25519.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package eddsa
|
||||
|
||||
import (
|
||||
_ed25519 "crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
)
|
||||
|
||||
const (
|
||||
ED25519JWACurve string = "Ed25519"
|
||||
ED25519AlgorithmID string = ED25519JWACurve
|
||||
)
|
||||
|
||||
// ED25519GeneratePrivateKey generates a new ED25519 private key
|
||||
func ED25519GeneratePrivateKey() (jwk.JWK, error) {
|
||||
publicKey, privateKey, err := _ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return jwk.JWK{}, err
|
||||
}
|
||||
|
||||
privKeyJwk := jwk.JWK{
|
||||
KTY: KeyType,
|
||||
CRV: ED25519JWACurve,
|
||||
D: base64.RawURLEncoding.EncodeToString(privateKey),
|
||||
X: base64.RawURLEncoding.EncodeToString(publicKey),
|
||||
}
|
||||
|
||||
return privKeyJwk, nil
|
||||
}
|
||||
|
||||
// ED25519Sign signs the given payload with the given private key
|
||||
func ED25519Sign(payload []byte, privateKey jwk.JWK) ([]byte, error) {
|
||||
privateKeyBytes, err := base64.RawURLEncoding.DecodeString(privateKey.D)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode d %w", err)
|
||||
}
|
||||
|
||||
signature := _ed25519.Sign(privateKeyBytes, payload)
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// ED25519Verify verifies the given signature against the given payload using the given public key
|
||||
func ED25519Verify(payload []byte, signature []byte, publicKey jwk.JWK) (bool, error) {
|
||||
publicKeyBytes, err := base64.RawURLEncoding.DecodeString(publicKey.X)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
legit := _ed25519.Verify(publicKeyBytes, payload, signature)
|
||||
return legit, nil
|
||||
}
|
||||
|
||||
// ED25519BytesToPublicKey deserializes the byte array into a jwk.JWK public key
|
||||
func ED25519BytesToPublicKey(input []byte) (jwk.JWK, error) {
|
||||
if len(input) != _ed25519.PublicKeySize {
|
||||
return jwk.JWK{}, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
return jwk.JWK{
|
||||
KTY: KeyType,
|
||||
CRV: ED25519JWACurve,
|
||||
X: base64.RawURLEncoding.EncodeToString(input),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ED25519PublicKeyToBytes serializes the given public key int a byte array
|
||||
func ED25519PublicKeyToBytes(publicKey jwk.JWK) ([]byte, error) {
|
||||
if publicKey.X == "" {
|
||||
return nil, errors.New("x must be set")
|
||||
}
|
||||
|
||||
publicKeyBytes, err := base64.RawURLEncoding.DecodeString(publicKey.X)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode x %w", err)
|
||||
}
|
||||
|
||||
return publicKeyBytes, nil
|
||||
}
|
||||
68
cli/pkg/web5/crypto/dsa/eddsa/ed25519_test.go
Normal file
68
cli/pkg/web5/crypto/dsa/eddsa/ed25519_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package eddsa_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"olares-cli/pkg/web5/crypto/dsa/eddsa"
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestED25519BytesToPublicKey_Bad(t *testing.T) {
|
||||
publicKeyBytes := []byte{0x00, 0x01, 0x02, 0x03}
|
||||
_, err := eddsa.ED25519BytesToPublicKey(publicKeyBytes)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestED25519BytesToPublicKey_Good(t *testing.T) {
|
||||
// vector taken from https://github.com/decentralized-identity/web5-js/blob/dids-new-crypto/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-public-key.json
|
||||
pubKeyHex := "7d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa"
|
||||
pubKeyBytes, err := hex.DecodeString(pubKeyHex)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jwk, err := eddsa.ED25519BytesToPublicKey(pubKeyBytes)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, eddsa.KeyType, jwk.KTY)
|
||||
assert.Equal(t, eddsa.ED25519JWACurve, jwk.CRV)
|
||||
assert.Equal(t, "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo", jwk.X)
|
||||
}
|
||||
|
||||
func TestED25519PublicKeyToBytes(t *testing.T) {
|
||||
// vector taken from: https://github.com/decentralized-identity/web5-spec/blob/main/test-vectors/crypto_ed25519/sign.json
|
||||
jwk := jwk.JWK{
|
||||
KTY: "OKP",
|
||||
CRV: eddsa.ED25519JWACurve,
|
||||
X: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
|
||||
}
|
||||
|
||||
pubKeyBytes, err := eddsa.ED25519PublicKeyToBytes(jwk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pubKeyB64URL := base64.RawURLEncoding.EncodeToString(pubKeyBytes)
|
||||
assert.Equal(t, jwk.X, pubKeyB64URL)
|
||||
}
|
||||
|
||||
func TestED25519PublicKeyToBytes_Bad(t *testing.T) {
|
||||
vectors := []jwk.JWK{
|
||||
{
|
||||
KTY: "OKP",
|
||||
CRV: eddsa.ED25519JWACurve,
|
||||
},
|
||||
{
|
||||
KTY: "OKP",
|
||||
CRV: eddsa.ED25519JWACurve,
|
||||
X: "=/---",
|
||||
},
|
||||
}
|
||||
|
||||
for _, jwk := range vectors {
|
||||
pubKeyBytes, err := eddsa.ED25519PublicKeyToBytes(jwk)
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.Equal(t, nil, pubKeyBytes)
|
||||
}
|
||||
}
|
||||
116
cli/pkg/web5/crypto/dsa/eddsa/eddsa.go
Normal file
116
cli/pkg/web5/crypto/dsa/eddsa/eddsa.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Package eddsa implements the EdDSA signature schemes as per RFC 8032
|
||||
// https://tools.ietf.org/html/rfc8032. Note: Currently only Ed25519 is supported
|
||||
package eddsa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"olares-cli/pkg/web5/jwk"
|
||||
)
|
||||
|
||||
const (
|
||||
JWA string = "EdDSA"
|
||||
KeyType string = "OKP"
|
||||
)
|
||||
|
||||
var algorithmIDs = map[string]bool{
|
||||
ED25519AlgorithmID: true,
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates an EdDSA private key for the given algorithm
|
||||
func GeneratePrivateKey(algorithmID string) (jwk.JWK, error) {
|
||||
switch algorithmID {
|
||||
case ED25519AlgorithmID:
|
||||
return ED25519GeneratePrivateKey()
|
||||
default:
|
||||
return jwk.JWK{}, fmt.Errorf("unsupported algorithm: %s", algorithmID)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPublicKey builds an EdDSA public key from the given EdDSA private key
|
||||
func GetPublicKey(privateKey jwk.JWK) jwk.JWK {
|
||||
return jwk.JWK{
|
||||
KTY: privateKey.KTY,
|
||||
CRV: privateKey.CRV,
|
||||
X: privateKey.X,
|
||||
}
|
||||
}
|
||||
|
||||
// Sign generates a cryptographic signature for the given payload with the given private key
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The function will automatically detect the given EdDSA cryptographic curve from the given private key
|
||||
func Sign(payload []byte, privateKey jwk.JWK) ([]byte, error) {
|
||||
if privateKey.D == "" {
|
||||
return nil, errors.New("d must be set")
|
||||
}
|
||||
|
||||
switch privateKey.CRV {
|
||||
case ED25519JWACurve:
|
||||
return ED25519Sign(payload, privateKey)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported curve: %s", privateKey.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify verifies the given signature over a given payload by the given public key
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The function will automatically detect the given EdDSA cryptographic curve from the given public key
|
||||
func Verify(payload []byte, signature []byte, publicKey jwk.JWK) (bool, error) {
|
||||
switch publicKey.CRV {
|
||||
case ED25519JWACurve:
|
||||
return ED25519Verify(payload, signature, publicKey)
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported curve: %s", publicKey.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// GetJWA returns the [JWA] for the given EdDSA key
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The only supported [JWA] is "EdDSA"
|
||||
//
|
||||
// [JWA]: https://datatracker.ietf.org/doc/html/rfc7518
|
||||
func GetJWA(jwk jwk.JWK) (string, error) {
|
||||
return JWA, nil
|
||||
}
|
||||
|
||||
// BytesToPublicKey deserializes the given byte array into a jwk.JWK for the given cryptographic algorithm
|
||||
func BytesToPublicKey(algorithmID string, input []byte) (jwk.JWK, error) {
|
||||
switch algorithmID {
|
||||
case ED25519AlgorithmID:
|
||||
return ED25519BytesToPublicKey(input)
|
||||
default:
|
||||
return jwk.JWK{}, fmt.Errorf("unsupported algorithm: %s", algorithmID)
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeyToBytes serializes the given public key into a byte array
|
||||
func PublicKeyToBytes(publicKey jwk.JWK) ([]byte, error) {
|
||||
switch publicKey.CRV {
|
||||
case ED25519JWACurve:
|
||||
return ED25519PublicKeyToBytes(publicKey)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported curve: %s", publicKey.CRV)
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsAlgorithmID informs as to whether or not the given algorithm ID is supported by this package
|
||||
func SupportsAlgorithmID(id string) bool {
|
||||
return algorithmIDs[id]
|
||||
}
|
||||
|
||||
// AlgorithmID returns the algorithm ID for the given jwk.JWK
|
||||
func AlgorithmID(jwk *jwk.JWK) (string, error) {
|
||||
switch jwk.CRV {
|
||||
case ED25519JWACurve:
|
||||
return ED25519AlgorithmID, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported curve: %s", jwk.CRV)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user