cli: change the module name of the cli (#1431)

This commit is contained in:
eball
2025-06-11 23:06:24 +08:00
committed by GitHub
parent f9072c9312
commit d484e41bbd
301 changed files with 6680 additions and 1059 deletions

View 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)
}
}

View 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
}

View 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)
}
}