mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 01:41:44 +00:00
feature/1220 origin handle exists method (#1241)
* feat: add docs and CI workflow for publishing to onsonr.dev * (refactor): Move hway,motr executables to their own repos * feat: simplify devnet and testnet configurations * refactor: update import path for didcrypto package * docs(networks): Add README with project overview, architecture, and community links * refactor: Move network configurations to deploy directory * build: update golang version to 1.23 * refactor: move logger interface to appropriate package * refactor: Move devnet configuration to networks/devnet * chore: improve release process with date variable * (chore): Move Crypto Library * refactor: improve code structure and readability in DID module * feat: integrate Trunk CI checks * ci: optimize CI workflow by removing redundant build jobs --------- Co-authored-by: Darp Alakun <i@prad.nu>
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||
"github.com/multiformats/go-multicodec"
|
||||
"github.com/multiformats/go-varint"
|
||||
)
|
||||
|
||||
// GenerateEd25519 generates an Ed25519 private key and the matching DID.
|
||||
// This is the RECOMMENDED algorithm.
|
||||
func GenerateEd25519() (crypto.PrivKey, DID, error) {
|
||||
priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, Undef, nil
|
||||
}
|
||||
did, err := FromPubKey(pub)
|
||||
return priv, did, err
|
||||
}
|
||||
|
||||
// GenerateRSA generates a RSA private key and the matching DID.
|
||||
func GenerateRSA() (crypto.PrivKey, DID, error) {
|
||||
// NIST Special Publication 800-57 Part 1 Revision 5
|
||||
// Section 5.6.1.1 (Table 2)
|
||||
// Paraphrased: 2048-bit RSA keys are secure until 2030 and 3072-bit keys are recommended for longer-term security.
|
||||
const keyLength = 3072
|
||||
|
||||
priv, pub, err := crypto.GenerateRSAKeyPair(keyLength, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, Undef, nil
|
||||
}
|
||||
did, err := FromPubKey(pub)
|
||||
return priv, did, err
|
||||
}
|
||||
|
||||
// GenerateSecp256k1 generates a Secp256k1 private key and the matching DID.
|
||||
func GenerateSecp256k1() (crypto.PrivKey, DID, error) {
|
||||
priv, pub, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, Undef, nil
|
||||
}
|
||||
did, err := FromPubKey(pub)
|
||||
return priv, did, err
|
||||
}
|
||||
|
||||
// GenerateECDSA generates an ECDSA private key and the matching DID
|
||||
// for the default P256 curve.
|
||||
func GenerateECDSA() (crypto.PrivKey, DID, error) {
|
||||
return GenerateECDSAWithCurve(P256)
|
||||
}
|
||||
|
||||
// GenerateECDSAWithCurve generates an ECDSA private key and matching
|
||||
// DID for the user-supplied curve
|
||||
func GenerateECDSAWithCurve(code multicodec.Code) (crypto.PrivKey, DID, error) {
|
||||
var curve elliptic.Curve
|
||||
|
||||
switch code {
|
||||
case P256:
|
||||
curve = elliptic.P256()
|
||||
case P384:
|
||||
curve = elliptic.P384()
|
||||
case P521:
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return nil, Undef, errors.New("unsupported ECDSA curve")
|
||||
}
|
||||
|
||||
priv, pub, err := crypto.GenerateECDSAKeyPairWithCurve(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, Undef, err
|
||||
}
|
||||
|
||||
did, err := FromPubKey(pub)
|
||||
|
||||
return priv, did, err
|
||||
}
|
||||
|
||||
// FromPrivKey is a convenience function that returns the DID associated
|
||||
// with the public key associated with the provided private key.
|
||||
func FromPrivKey(privKey crypto.PrivKey) (DID, error) {
|
||||
return FromPubKey(privKey.GetPublic())
|
||||
}
|
||||
|
||||
// FromPubKey returns a did:key constructed from the provided public key.
|
||||
func FromPubKey(pubKey crypto.PubKey) (DID, error) {
|
||||
var code multicodec.Code
|
||||
|
||||
switch pubKey.Type() {
|
||||
case pb.KeyType_Ed25519:
|
||||
code = multicodec.Ed25519Pub
|
||||
case pb.KeyType_RSA:
|
||||
code = RSA
|
||||
case pb.KeyType_Secp256k1:
|
||||
code = Secp256k1
|
||||
case pb.KeyType_ECDSA:
|
||||
var err error
|
||||
if code, err = codeForCurve(pubKey); err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
default:
|
||||
return Undef, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
if pubKey.Type() == pb.KeyType_ECDSA && code == Secp256k1 {
|
||||
var err error
|
||||
|
||||
pubKey, err = coerceECDSAToSecp256k1(pubKey)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
}
|
||||
|
||||
var bytes []byte
|
||||
|
||||
switch pubKey.Type() {
|
||||
case pb.KeyType_ECDSA:
|
||||
pkix, err := pubKey.Raw()
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
|
||||
publicKey, err := x509.ParsePKIXPublicKey(pkix)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
|
||||
ecdsaPublicKey := publicKey.(*ecdsa.PublicKey)
|
||||
|
||||
bytes = elliptic.MarshalCompressed(ecdsaPublicKey.Curve, ecdsaPublicKey.X, ecdsaPublicKey.Y)
|
||||
case pb.KeyType_Ed25519, pb.KeyType_Secp256k1:
|
||||
var err error
|
||||
|
||||
if bytes, err = pubKey.Raw(); err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
case pb.KeyType_RSA:
|
||||
var err error
|
||||
|
||||
pkix, err := pubKey.Raw()
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
|
||||
publicKey, err := x509.ParsePKIXPublicKey(pkix)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
|
||||
bytes = x509.MarshalPKCS1PublicKey(publicKey.(*rsa.PublicKey))
|
||||
}
|
||||
|
||||
return DID{
|
||||
code: code,
|
||||
bytes: string(append(varint.ToUvarint(uint64(code)), bytes...)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToPubKey returns the crypto.PubKey encapsulated in the DID formed by
|
||||
// parsing the provided string.
|
||||
func ToPubKey(s string) (crypto.PubKey, error) {
|
||||
id, err := Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return id.PubKey()
|
||||
}
|
||||
|
||||
func codeForCurve(pubKey crypto.PubKey) (multicodec.Code, error) {
|
||||
stdPub, err := crypto.PubKeyToStdKey(pubKey)
|
||||
if err != nil {
|
||||
return multicodec.Identity, err
|
||||
}
|
||||
|
||||
ecdsaPub, ok := stdPub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return multicodec.Identity, errors.New("failed to assert type for code to curve")
|
||||
}
|
||||
|
||||
switch ecdsaPub.Curve {
|
||||
case elliptic.P256():
|
||||
return P256, nil
|
||||
case elliptic.P384():
|
||||
return P384, nil
|
||||
case elliptic.P521():
|
||||
return P521, nil
|
||||
case secp256k1.S256():
|
||||
return Secp256k1, nil
|
||||
default:
|
||||
return multicodec.Identity, fmt.Errorf("unsupported ECDSA curve: %s", ecdsaPub.Curve.Params().Name)
|
||||
}
|
||||
}
|
||||
|
||||
// secp256k1.S256 is a valid ECDSA curve, but the go-libp2p/core/crypto
|
||||
// package treats it as a different type and has a different format for
|
||||
// the raw bytes of the public key.
|
||||
//
|
||||
// If a valid ECDSA public key was created using the secp256k1.S256 curve,
|
||||
// this function will "convert" it from a crypto.ECDSAPubKey to a
|
||||
// crypto.Secp256k1PublicKey.
|
||||
func coerceECDSAToSecp256k1(pubKey crypto.PubKey) (crypto.PubKey, error) {
|
||||
stdPub, err := crypto.PubKeyToStdKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ecdsaPub, ok := stdPub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to assert type for secp256k1 coersion")
|
||||
}
|
||||
|
||||
ecdsaPubBytes := append([]byte{0x04}, append(ecdsaPub.X.Bytes(), ecdsaPub.Y.Bytes()...)...)
|
||||
|
||||
secp256k1Pub, err := secp256k1.ParsePubKey(ecdsaPubBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cryptoPub := crypto.Secp256k1PublicKey(*secp256k1Pub)
|
||||
|
||||
return &cryptoPub, nil
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
libp2p_crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||
"github.com/multiformats/go-multicodec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/onsonr/sonr/internal/didcrypto"
|
||||
)
|
||||
|
||||
const (
|
||||
exampleDIDStr = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
||||
examplePubKeyStr = "Lm/M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY="
|
||||
)
|
||||
|
||||
func TestFromPubKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, ecdsaP256, err := libp2p_crypto.GenerateECDSAKeyPairWithCurve(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
_, ecdsaP384, err := libp2p_crypto.GenerateECDSAKeyPairWithCurve(elliptic.P384(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
_, ecdsaP521, err := libp2p_crypto.GenerateECDSAKeyPairWithCurve(elliptic.P521(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
_, ecdsaSecp256k1, err := libp2p_crypto.GenerateECDSAKeyPairWithCurve(secp256k1.S256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
_, ed25519, err := libp2p_crypto.GenerateEd25519Key(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
_, rsa, err := libp2p_crypto.GenerateRSAKeyPair(2048, rand.Reader)
|
||||
require.NoError(t, err)
|
||||
_, secp256k1PubKey1, err := libp2p_crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
test := func(pub libp2p_crypto.PubKey, code multicodec.Code) func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
return func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id, err := crypto.FromPubKey(pub)
|
||||
require.NoError(t, err)
|
||||
p, err := id.PubKey()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pub, p)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("ECDSA with P256 curve", test(ecdsaP256, crypto.P256))
|
||||
t.Run("ECDSA with P384 curve", test(ecdsaP384, crypto.P384))
|
||||
t.Run("ECDSA with P521 curve", test(ecdsaP521, crypto.P521))
|
||||
t.Run("Ed25519", test(ed25519, crypto.Ed25519))
|
||||
t.Run("RSA", test(rsa, crypto.RSA))
|
||||
t.Run("secp256k1", test(secp256k1PubKey1, crypto.Secp256k1))
|
||||
|
||||
t.Run("ECDSA with secp256k1 curve (coerced)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id, err := crypto.FromPubKey(ecdsaSecp256k1)
|
||||
require.NoError(t, err)
|
||||
p, err := id.PubKey()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pb.KeyType_Secp256k1, p.Type())
|
||||
})
|
||||
|
||||
t.Run("unmarshaled example key (secp256k1)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id, err := crypto.FromPubKey(examplePubKey(t))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, exampleDID(t), id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestToPubKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pubKey, err := crypto.ToPubKey(exampleDIDStr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, examplePubKey(t), pubKey)
|
||||
}
|
||||
|
||||
func exampleDID(t *testing.T) crypto.DID {
|
||||
t.Helper()
|
||||
|
||||
id, err := crypto.Parse(exampleDIDStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func examplePubKey(t *testing.T) libp2p_crypto.PubKey {
|
||||
t.Helper()
|
||||
|
||||
pubKeyCfg, err := libp2p_crypto.ConfigDecodeKey(examplePubKeyStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKey, err := libp2p_crypto.UnmarshalEd25519PublicKey(pubKeyCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
return pubKey
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-multicodec"
|
||||
varint "github.com/multiformats/go-varint"
|
||||
)
|
||||
|
||||
// Signature algorithms from the [did:key specification]
|
||||
//
|
||||
// [did:key specification]: https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
|
||||
const (
|
||||
X25519 = multicodec.X25519Pub
|
||||
Ed25519 = multicodec.Ed25519Pub // UCAN required/recommended
|
||||
P256 = multicodec.P256Pub // UCAN required
|
||||
P384 = multicodec.P384Pub
|
||||
P521 = multicodec.P521Pub
|
||||
Secp256k1 = multicodec.Secp256k1Pub // UCAN required
|
||||
RSA = multicodec.RsaPub
|
||||
)
|
||||
|
||||
// Undef can be used to represent a nil or undefined DID, using DID{}
|
||||
// directly is also acceptable.
|
||||
var Undef = DID{}
|
||||
|
||||
// DID is a Decentralized Identifier of the did:key type, directly holding a cryptographic public key.
|
||||
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
|
||||
type DID struct {
|
||||
code multicodec.Code
|
||||
bytes string // as string instead of []byte to allow the == operator
|
||||
}
|
||||
|
||||
// Parse returns the DID from the string representation or an error if
|
||||
// the prefix and method are incorrect, if an unknown encryption algorithm
|
||||
// is specified or if the method-specific-identifier's bytes don't
|
||||
// represent a public key for the specified encryption algorithm.
|
||||
func Parse(str string) (DID, error) {
|
||||
const keyPrefix = "did:key:"
|
||||
|
||||
if !strings.HasPrefix(str, keyPrefix) {
|
||||
return Undef, fmt.Errorf("must start with 'did:key'")
|
||||
}
|
||||
|
||||
baseCodec, bytes, err := mbase.Decode(str[len(keyPrefix):])
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
if baseCodec != mbase.Base58BTC {
|
||||
return Undef, fmt.Errorf("not Base58BTC encoded")
|
||||
}
|
||||
code, _, err := varint.FromUvarint(bytes)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
switch multicodec.Code(code) {
|
||||
case Ed25519, P256, Secp256k1, RSA:
|
||||
return DID{bytes: string(bytes), code: multicodec.Code(code)}, nil
|
||||
default:
|
||||
return Undef, fmt.Errorf("unsupported did:key multicodec: 0x%x", code)
|
||||
}
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics instead of returning an error.
|
||||
func MustParse(str string) DID {
|
||||
did, err := Parse(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return did
|
||||
}
|
||||
|
||||
// Defined tells if the DID is defined, not equal to Undef.
|
||||
func (d DID) Defined() bool {
|
||||
return d.code != 0 || len(d.bytes) > 0
|
||||
}
|
||||
|
||||
// PubKey returns the public key encapsulated by the did:key.
|
||||
func (d DID) PubKey() (crypto.PubKey, error) {
|
||||
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
|
||||
X25519: crypto.UnmarshalEd25519PublicKey,
|
||||
Ed25519: crypto.UnmarshalEd25519PublicKey,
|
||||
P256: ecdsaPubKeyUnmarshaler(elliptic.P256()),
|
||||
P384: ecdsaPubKeyUnmarshaler(elliptic.P384()),
|
||||
P521: ecdsaPubKeyUnmarshaler(elliptic.P521()),
|
||||
Secp256k1: crypto.UnmarshalSecp256k1PublicKey,
|
||||
RSA: rsaPubKeyUnmarshaller,
|
||||
}[d.code]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
|
||||
}
|
||||
|
||||
codeSize := varint.UvarintSize(uint64(d.code))
|
||||
return unmarshaler([]byte(d.bytes)[codeSize:])
|
||||
}
|
||||
|
||||
// String formats the decentralized identity document (DID) as a string.
|
||||
func (d DID) String() string {
|
||||
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
|
||||
return "did:key:" + key
|
||||
}
|
||||
|
||||
func ecdsaPubKeyUnmarshaler(curve elliptic.Curve) crypto.PubKeyUnmarshaller {
|
||||
return func(data []byte) (crypto.PubKey, error) {
|
||||
x, y := elliptic.UnmarshalCompressed(curve, data)
|
||||
|
||||
ecdsaPublicKey := &ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
|
||||
pkix, err := x509.MarshalPKIXPublicKey(ecdsaPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return crypto.UnmarshalECDSAPublicKey(pkix)
|
||||
}
|
||||
}
|
||||
|
||||
func rsaPubKeyUnmarshaller(data []byte) (crypto.PubKey, error) {
|
||||
rsaPublicKey, err := x509.ParsePKCS1PublicKey(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkix, err := x509.MarshalPKIXPublicKey(rsaPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return crypto.UnmarshalRsaPublicKey(pkix)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseDIDKey(t *testing.T) {
|
||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
d, err := Parse(str)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, str, d.String())
|
||||
}
|
||||
|
||||
func TestMustParseDIDKey(t *testing.T) {
|
||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
require.NotPanics(t, func() {
|
||||
d := MustParse(str)
|
||||
require.Equal(t, str, d.String())
|
||||
})
|
||||
str = "did:key:z7Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
require.Panics(t, func() {
|
||||
MustParse(str)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEquivalence(t *testing.T) {
|
||||
undef0 := DID{}
|
||||
undef1 := Undef
|
||||
|
||||
did0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||
require.NoError(t, err)
|
||||
did1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, undef0 == undef1)
|
||||
require.False(t, undef0 == did0)
|
||||
require.True(t, did0 == did1)
|
||||
require.False(t, undef1 == did1)
|
||||
}
|
||||
Reference in New Issue
Block a user