feature/1114 implement account interface (#1167)

- **refactor: move session-related code to middleware package**
- **refactor: update PKL build process and adjust related
configurations**
- **feat: integrate base.cosmos.v1 Genesis module**
- **refactor: pass session context to modal rendering functions**
- **refactor: move nebula package to app directory and update templ
version**
- **refactor: Move home section video view to dedicated directory**
- **refactor: remove unused views file**
- **refactor: move styles and UI components to global scope**
- **refactor: Rename images.go to cdn.go**
- **feat: Add Empty State Illustrations**
- **refactor: Consolidate Vault Index Logic**
- **fix: References to App.wasm and remove Vault Directory embedded CDN
files**
- **refactor: Move CDN types to Models**
- **fix: Correct line numbers in templ error messages for
arch_templ.go**
- **refactor: use common types for peer roles**
- **refactor: move common types and ORM to a shared package**
- **fix: Config import dwn**
- **refactor: move nebula directory to app**
- **feat: Rebuild nebula**
- **fix: correct file paths in panels templates**
- **feat: Remove duplicate types**
- **refactor: Move dwn to pkg/core**
- **refactor: Binary Structure**
- **feat: Introduce Crypto Pkg**
- **fix: Broken Process Start**
- **feat: Update pkg/* structure**
- **feat: Refactor PKL Structure**
- **build: update pkl build process**
- **chore: Remove Empty Files**
- **refactor: remove unused macaroon package**
- **feat: Add WebAwesome Components**
- **refactor: consolidate build and generation tasks into a single
taskfile, remove redundant makefile targets**
- **refactor: refactor server and move components to pkg/core/dwn**
- **build: update go modules**
- **refactor: move gateway logic into dedicated hway command**
- **feat: Add KSS (Krawczyk-Song-Song) MPC cryptography module**
- **feat: Implement MPC-based JWT signing and UCAN token generation**
- **feat: add support for MPC-based JWT signing**
- **feat: Implement MPC-based UCAN capabilities for smart accounts**
- **feat: add address field to keyshareSource**
- **feat: Add comprehensive MPC test suite for keyshares, UCAN tokens,
and token attenuations**
- **refactor: improve MPC keyshare management and signing process**
- **feat: enhance MPC capability hierarchy documentation**
- **refactor: rename GenerateKeyshares function to NewKeyshareSource for
clarity**
- **refactor: remove unused Ethereum address computation**
- **feat: Add HasHandle and IsAuthenticated methods to HTTPContext**
- **refactor: Add context.Context support to session HTTPContext**
- **refactor: Resolve context interface conflicts in HTTPContext**
- **feat: Add session ID context key and helper functions**
- **feat: Update WebApp Page Rendering**
- **refactor: Simplify context management by using single HTTPContext
key**
- **refactor: Simplify HTTPContext creation and context management in
session middleware**
- **refactor: refactor session middleware to use a single data
structure**
- **refactor: Simplify HTTPContext implementation and session data
handling**
- **refactor: Improve session context handling and prevent nil pointer
errors**
- **refactor: Improve session context handling with nil safety and type
support**
- **refactor: improve session data injection**
- **feat: add full-screen modal component and update registration flow**
- **chore: add .air.toml to .gitignore**
- **feat: add Air to devbox and update dependencies**
This commit is contained in:
Prad Nukala
2024-11-23 01:28:58 -05:00
committed by GitHub
parent bf94277b0f
commit 89989fa102
549 changed files with 74162 additions and 9856 deletions
+20 -39
View File
@@ -1,44 +1,28 @@
package controller
import (
"github.com/onsonr/crypto/mpc"
commonv1 "github.com/onsonr/sonr/pkg/common/types"
"github.com/onsonr/sonr/x/did/types"
"github.com/onsonr/sonr/pkg/crypto/mpc"
)
type ControllerI interface {
ChainID() string
GetPubKey() *commonv1.PubKey
// GetPubKey() *commonv1.PubKey
SonrAddress() string
RawPublicKey() []byte
}
func New(shares []mpc.Share) (ControllerI, error) {
var (
valKs = shares[0]
userKs = shares[1]
)
pb, err := valKs.PublicKey()
if err != nil {
return nil, err
}
sonrAddr, err := types.ComputeSonrAddr(pb)
if err != nil {
return nil, err
}
func New(src mpc.KeyshareSource) (ControllerI, error) {
return &controller{
valKs: valKs,
userKs: userKs,
address: sonrAddr,
publicKey: pb,
src: src,
address: src.Address(),
publicKey: src.PublicKey(),
did: src.Issuer(),
chainID: "sonr-testnet-1",
}, nil
}
type controller struct {
userKs mpc.Share
valKs mpc.Share
src mpc.KeyshareSource
address string
chainID string
publicKey []byte
@@ -49,25 +33,22 @@ func (c *controller) ChainID() string {
return c.chainID
}
func (c *controller) GetPubKey() *commonv1.PubKey {
return &commonv1.PubKey{
KeyType: "ecdsa",
RawKey: &commonv1.RawKey{
Algorithm: "secp256k1",
Key: c.publicKey,
},
Role: "authentication",
}
}
//
// func (c *controller) GetPubKey() *commonv1.PubKey {
// return &commonv1.PubKey{
// KeyType: "ecdsa",
// RawKey: &commonv1.RawKey{
// Algorithm: "secp256k1",
// Key: c.publicKey,
// },
// Role: "authentication",
// }
// }
func (c *controller) RawPublicKey() []byte {
return c.publicKey
}
// func (c *controller) StdPublicKey() cryptotypes.PubKey {
// return c.stdPubKey
// }
func (c *controller) SonrAddress() string {
return c.address
}
-1
View File
@@ -1 +0,0 @@
package signer
-1
View File
@@ -1 +0,0 @@
package signer
-1
View File
@@ -1 +0,0 @@
package wallet
-1
View File
@@ -1 +0,0 @@
package wallet
+2 -2
View File
@@ -5,14 +5,14 @@ import (
"cosmossdk.io/log"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/onsonr/crypto/mpc"
"github.com/onsonr/sonr/pkg/crypto/mpc"
"github.com/onsonr/sonr/x/did/controller"
"github.com/onsonr/sonr/x/did/types"
)
func (k Keeper) NewController(ctx sdk.Context) (controller.ControllerI, error) {
shares, err := mpc.GenerateKeyshares()
shares, err := mpc.NewKeyshareSource()
if err != nil {
return nil, err
}
@@ -1,5 +1,4 @@
// Package accountstd exports the types and functions that are used by developers to implement smart accounts.
package accstd
package types
import (
"bytes"
@@ -11,7 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/onsonr/sonr/pkg/core/transaction"
"github.com/onsonr/sonr/x/did/types/accstd/internal/accounts"
"github.com/onsonr/sonr/x/did/types/internal/accounts"
)
var (
@@ -19,22 +18,22 @@ var (
ErrInvalidType = errors.New("invalid type")
)
// Interface is the exported interface of an Account.
type Interface = accounts.Account
// AccountsInterface is the exported interface of an Account.
type AccountsInterface = accounts.Account
// ExecuteBuilder is the exported type of ExecuteBuilder.
type ExecuteBuilder = accounts.ExecuteBuilder
// AccountsExecuteBuilder is the exported type of AccountsExecuteBuilder.
type AccountsExecuteBuilder = accounts.ExecuteBuilder
// QueryBuilder is the exported type of QueryBuilder.
type QueryBuilder = accounts.QueryBuilder
// AccountsQueryBuilder is the exported type of AccountsQueryBuilder.
type AccountsQueryBuilder = accounts.QueryBuilder
// InitBuilder is the exported type of InitBuilder.
type InitBuilder = accounts.InitBuilder
// AccountsInitBuilder is the exported type of AccountsInitBuilder.
type AccountsInitBuilder = accounts.InitBuilder
// AccountCreatorFunc is the exported type of AccountCreatorFunc.
type AccountCreatorFunc = accounts.AccountCreatorFunc
func DIAccount[A Interface](name string, constructor func(deps Dependencies) (A, error)) DepinjectAccount {
func DIAccount[A AccountsInterface](name string, constructor func(deps Dependencies) (A, error)) DepinjectAccount {
return DepinjectAccount{MakeAccount: AddAccount(name, constructor)}
}
@@ -47,31 +46,31 @@ func (DepinjectAccount) IsManyPerContainerType() {}
// Dependencies is the exported type of Dependencies.
type Dependencies = accounts.Dependencies
func RegisterExecuteHandler[
func RegisterAccountsExecuteHandler[
Req any, ProtoReq accounts.ProtoMsgG[Req], Resp any, ProtoResp accounts.ProtoMsgG[Resp],
](router *ExecuteBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
](router *AccountsExecuteBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
) {
accounts.RegisterExecuteHandler(router, handler)
}
// RegisterQueryHandler registers a query handler for a smart account that uses protobuf.
func RegisterQueryHandler[
// RegisterAccountsQueryHandler registers a query handler for a smart account that uses protobuf.
func RegisterAccountsQueryHandler[
Req any, ProtoReq accounts.ProtoMsgG[Req], Resp any, ProtoResp accounts.ProtoMsgG[Resp],
](router *QueryBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
](router *AccountsQueryBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
) {
accounts.RegisterQueryHandler(router, handler)
}
// RegisterInitHandler registers an initialisation handler for a smart account that uses protobuf.
func RegisterInitHandler[
// RegisterAccountsInitHandler registers an initialisation handler for a smart account that uses protobuf.
func RegisterAccountsInitHandler[
Req any, ProtoReq accounts.ProtoMsgG[Req], Resp any, ProtoResp accounts.ProtoMsgG[Resp],
](router *InitBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
](router *AccountsInitBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
) {
accounts.RegisterInitHandler(router, handler)
}
// AddAccount is a helper function to add a smart account to the list of smart accounts.
func AddAccount[A Interface](name string, constructor func(deps Dependencies) (A, error)) AccountCreatorFunc {
func AddAccount[A AccountsInterface](name string, constructor func(deps Dependencies) (A, error)) AccountCreatorFunc {
return func(deps accounts.Dependencies) (string, accounts.Account, error) {
acc, err := constructor(deps)
return name, acc, err
+52
View File
@@ -0,0 +1,52 @@
package types
import (
"github.com/cosmos/cosmos-sdk/types/bech32"
)
// ComputeSonrAddr computes the Sonr address from a public key
func ComputeSonrAddr(pk []byte) (string, error) {
sonrAddr, err := bech32.ConvertAndEncode("idx", pk)
if err != nil {
return "", err
}
return sonrAddr, nil
}
// ComputeBitcoinAddr computes the Bitcoin address from a public key
func ComputeBitcoinAddr(pk []byte) (string, error) {
btcAddr, err := bech32.ConvertAndEncode("bc", pk)
if err != nil {
return "", err
}
return btcAddr, nil
}
//
// // ComputeEthereumAddr computes the Ethereum address from a public key
// func ComputeEthereumAddr(pk *ecdsa.PublicKey) string {
// // Generate Ethereum address
// address := ethcrypto.PubkeyToAddress(*pk)
//
// // Apply ERC-55 checksum encoding
// addr := address.Hex()
// addr = strings.ToLower(addr)
// addr = strings.TrimPrefix(addr, "0x")
// hash := sha3.NewLegacyKeccak256()
// hash.Write([]byte(addr))
// hashBytes := hash.Sum(nil)
//
// result := "0x"
// for i, c := range addr {
// if c >= '0' && c <= '9' {
// result += string(c)
// } else {
// if hashBytes[i/2]>>(4-i%2*4)&0xf >= 8 {
// result += strings.ToUpper(string(c))
// } else {
// result += string(c)
// }
// }
// }
// return result
// }
-56
View File
@@ -1,56 +0,0 @@
package types
import (
"crypto/ecdsa"
"strings"
"github.com/cosmos/cosmos-sdk/types/bech32"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"golang.org/x/crypto/sha3"
)
// ComputeSonrAddr computes the Sonr address from a public key
func ComputeSonrAddr(pk []byte) (string, error) {
sonrAddr, err := bech32.ConvertAndEncode("idx", pk)
if err != nil {
return "", err
}
return sonrAddr, nil
}
// ComputeBitcoinAddr computes the Bitcoin address from a public key
func ComputeBitcoinAddr(pk []byte) (string, error) {
btcAddr, err := bech32.ConvertAndEncode("bc", pk)
if err != nil {
return "", err
}
return btcAddr, nil
}
// ComputeEthereumAddr computes the Ethereum address from a public key
func ComputeEthereumAddr(pk *ecdsa.PublicKey) string {
// Generate Ethereum address
address := ethcrypto.PubkeyToAddress(*pk)
// Apply ERC-55 checksum encoding
addr := address.Hex()
addr = strings.ToLower(addr)
addr = strings.TrimPrefix(addr, "0x")
hash := sha3.NewLegacyKeccak256()
hash.Write([]byte(addr))
hashBytes := hash.Sum(nil)
result := "0x"
for i, c := range addr {
if c >= '0' && c <= '9' {
result += string(c)
} else {
if hashBytes[i/2]>>(4-i%2*4)&0xf >= 8 {
result += strings.ToUpper(string(c))
} else {
result += string(c)
}
}
}
return result
}
+231
View File
@@ -0,0 +1,231 @@
package did
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
}
// GenerateEd25519 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
}
+108
View File
@@ -0,0 +1,108 @@
package did_test
import (
"crypto/elliptic"
"crypto/rand"
"testing"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"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/x/did/types/did"
)
const (
exampleDIDStr = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
examplePubKeyStr = "Lm/M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY="
)
func TestFromPubKey(t *testing.T) {
t.Parallel()
_, ecdsaP256, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P256(), rand.Reader)
require.NoError(t, err)
_, ecdsaP384, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P384(), rand.Reader)
require.NoError(t, err)
_, ecdsaP521, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P521(), rand.Reader)
require.NoError(t, err)
_, ecdsaSecp256k1, err := crypto.GenerateECDSAKeyPairWithCurve(secp256k1.S256(), rand.Reader)
require.NoError(t, err)
_, ed25519, err := crypto.GenerateEd25519Key(rand.Reader)
require.NoError(t, err)
_, rsa, err := crypto.GenerateRSAKeyPair(2048, rand.Reader)
require.NoError(t, err)
_, secp256k1PubKey1, err := crypto.GenerateSecp256k1Key(rand.Reader)
require.NoError(t, err)
test := func(pub crypto.PubKey, code multicodec.Code) func(t *testing.T) {
t.Helper()
return func(t *testing.T) {
t.Parallel()
id, err := did.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, did.P256))
t.Run("ECDSA with P384 curve", test(ecdsaP384, did.P384))
t.Run("ECDSA with P521 curve", test(ecdsaP521, did.P521))
t.Run("Ed25519", test(ed25519, did.Ed25519))
t.Run("RSA", test(rsa, did.RSA))
t.Run("secp256k1", test(secp256k1PubKey1, did.Secp256k1))
t.Run("ECDSA with secp256k1 curve (coerced)", func(t *testing.T) {
t.Parallel()
id, err := did.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 := did.FromPubKey(examplePubKey(t))
require.NoError(t, err)
require.Equal(t, exampleDID(t), id)
})
}
func TestToPubKey(t *testing.T) {
t.Parallel()
pubKey, err := did.ToPubKey(exampleDIDStr)
require.NoError(t, err)
require.Equal(t, examplePubKey(t), pubKey)
}
func exampleDID(t *testing.T) did.DID {
t.Helper()
id, err := did.Parse(exampleDIDStr)
require.NoError(t, err)
return id
}
func examplePubKey(t *testing.T) crypto.PubKey {
t.Helper()
pubKeyCfg, err := crypto.ConfigDecodeKey(examplePubKeyStr)
require.NoError(t, err)
pubKey, err := crypto.UnmarshalEd25519PublicKey(pubKeyCfg)
require.NoError(t, err)
return pubKey
}
+140
View File
@@ -0,0 +1,140 @@
package did
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)
}
+41
View File
@@ -0,0 +1,41 @@
package did
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)
}
@@ -9,7 +9,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/onsonr/sonr/pkg/core/transaction"
"github.com/onsonr/sonr/x/did/types/accstd/internal/prefixstore"
"github.com/onsonr/sonr/x/did/types/internal/prefixstore"
)
var AccountStatePrefix = collections.NewPrefix(255)
+4 -4
View File
@@ -4,10 +4,10 @@ import (
"encoding/json"
fmt "fmt"
"github.com/onsonr/sonr/pkg/motr/types/orm/keyalgorithm"
"github.com/onsonr/sonr/pkg/motr/types/orm/keycurve"
"github.com/onsonr/sonr/pkg/motr/types/orm/keyencoding"
"github.com/onsonr/sonr/pkg/motr/types/orm/keyrole"
"github.com/onsonr/sonr/pkg/common/models/keyalgorithm"
"github.com/onsonr/sonr/pkg/common/models/keycurve"
"github.com/onsonr/sonr/pkg/common/models/keyencoding"
"github.com/onsonr/sonr/pkg/common/models/keyrole"
)
// DefaultParams returns default module parameters.
@@ -1,4 +1,4 @@
package wallet
package types
import (
"bytes"
@@ -7,15 +7,13 @@ import (
sdk "github.com/cosmos/cosmos-sdk/crypto/types"
"google.golang.org/protobuf/proto"
commonv1 "github.com/onsonr/sonr/pkg/common/types"
)
type PubKeyI interface {
GetRole() string
GetKeyType() string
GetRawKey() *commonv1.RawKey
GetJwk() *commonv1.JSONWebKey
// GetRawKey() *commonv1.RawKey
// GetJwk() *commonv1.JSONWebKey
}
// PubKey defines a generic pubkey.
@@ -1,4 +1,4 @@
package signer
package types
import (
"context"
+3 -3
View File
@@ -15,7 +15,7 @@ import (
"github.com/ipfs/kubo/client/rpc"
apiv1 "github.com/onsonr/sonr/api/vault/v1"
dwngen "github.com/onsonr/sonr/pkg/motr/config"
"github.com/onsonr/sonr/pkg/core/dwn"
didkeeper "github.com/onsonr/sonr/x/did/keeper"
"github.com/onsonr/sonr/x/vault/types"
)
@@ -100,13 +100,13 @@ func NewKeeper(
}
// currentSchema returns the current schema
func (k Keeper) currentSchema(ctx sdk.Context) (*dwngen.Schema, error) {
func (k Keeper) currentSchema(ctx sdk.Context) (*dwn.Schema, error) {
p, err := k.Params.Get(ctx)
if err != nil {
return nil, err
}
schema := p.Schema
return &dwngen.Schema{
return &dwn.Schema{
Version: int(schema.Version),
Account: schema.Account,
Asset: schema.Asset,
+4 -4
View File
@@ -6,8 +6,8 @@ import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/onsonr/crypto/mpc"
"github.com/onsonr/sonr/pkg/crypto/mpc"
"github.com/onsonr/sonr/x/did/controller"
"github.com/onsonr/sonr/x/vault/types"
)
@@ -65,7 +65,7 @@ func (k Querier) Allocate(goCtx context.Context, req *types.QueryAllocateRequest
}
// 2. Generate MPC Keyshares for new Account
shares, err := mpc.GenerateKeyshares()
shares, err := mpc.NewKeyshareSource()
if err != nil {
ctx.Logger().Error(fmt.Sprintf("Error generating keyshares: %s", err.Error()))
return nil, types.ErrInvalidSchema.Wrap(err.Error())
@@ -79,14 +79,14 @@ func (k Querier) Allocate(goCtx context.Context, req *types.QueryAllocateRequest
}
// 4. Create a new vault PWA for service-worker
v, err := types.NewVault("", con.SonrAddress(), con.ChainID(), sch)
v, err := types.SpawnVault("", con.SonrAddress(), con.ChainID(), sch)
if err != nil {
ctx.Logger().Error(fmt.Sprintf("Error creating vault: %s", err.Error()))
return nil, types.ErrInvalidSchema.Wrap(err.Error())
}
// 5. Add to IPFS and Return CID for User Claims in Gateway
cid, err := k.ipfsClient.Unixfs().Add(context.Background(), v.FS)
cid, err := k.ipfsClient.Unixfs().Add(context.Background(), v)
if err != nil {
ctx.Logger().Error(fmt.Sprintf("Error adding to IPFS: %s", err.Error()))
return nil, types.ErrVaultAssembly.Wrap(err.Error())
+10 -9
View File
@@ -3,7 +3,8 @@ package types
import (
"encoding/json"
"github.com/onsonr/sonr/pkg/motr/types/orm"
"github.com/onsonr/sonr/pkg/common"
orm "github.com/onsonr/sonr/pkg/common/models"
)
// DefaultParams returns default module parameters.
@@ -35,13 +36,13 @@ func (p Params) Validate() error {
// DefaultSchema returns the default schema
func DefaultSchema() *Schema {
return &Schema{
Version: orm.SchemaVersion,
Account: orm.GetSchema(&orm.Account{}),
Asset: orm.GetSchema(&orm.Asset{}),
Chain: orm.GetSchema(&orm.Chain{}),
Credential: orm.GetSchema(&orm.Credential{}),
Grant: orm.GetSchema(&orm.Grant{}),
Keyshare: orm.GetSchema(&orm.Keyshare{}),
Profile: orm.GetSchema(&orm.Profile{}),
Version: common.SchemaVersion,
Account: common.GetSchema(&orm.Account{}),
Asset: common.GetSchema(&orm.Asset{}),
Chain: common.GetSchema(&orm.Chain{}),
Credential: common.GetSchema(&orm.Credential{}),
Grant: common.GetSchema(&orm.Grant{}),
Keyshare: common.GetSchema(&orm.Keyshare{}),
Profile: common.GetSchema(&orm.Profile{}),
}
}
+42
View File
@@ -0,0 +1,42 @@
package types
import (
"github.com/ipfs/boxo/files"
"github.com/onsonr/sonr/pkg/core/dwn"
"github.com/onsonr/sonr/x/vault/types/static"
)
const (
kFileNameConfigJSON = "dwn.json"
kFileNameIndexHTML = "index.html"
kFileNameWorkerJS = "sw.js"
)
type Vault = files.Directory
func SpawnVault(keyshareJSON string, adddress string, chainID string, schema *dwn.Schema) (Vault, error) {
dwnCfg := &dwn.Config{
MotrKeyshare: keyshareJSON,
MotrAddress: adddress,
IpfsGatewayUrl: "https://ipfs.sonr.land",
SonrApiUrl: "https://api.sonr.land",
SonrRpcUrl: "https://rpc.sonr.land",
SonrChainId: chainID,
VaultSchema: schema,
}
cnf, err := dwnCfg.MarshalJSON()
if err != nil {
return nil, err
}
return setupVaultDirectory(cnf), nil
}
// spawnVaultDirectory creates a new directory with the default files
func setupVaultDirectory(cfgBz []byte) files.Directory {
return files.NewMapDirectory(map[string]files.Node{
kFileNameConfigJSON: files.NewBytesFile(cfgBz),
kFileNameIndexHTML: files.NewBytesFile(static.IndexHTML),
kFileNameWorkerJS: files.NewBytesFile(static.WorkerJS),
})
}
+36
View File
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sonr DWN</title>
<script type="text/javascript">
if ("serviceWorker" in navigator) {
// Register the service worker
window.addEventListener("load", function () {
navigator.serviceWorker
.register("/sw.js")
.then(function (registration) {
console.log(
"Service Worker registered with scope:",
registration.scope,
);
})
.catch(function (error) {
console.log("Service Worker registration failed:", error);
});
});
}
</script>
</head>
<body
class="flex items-center justify-center h-full bg-zinc-50 lg:p-24 md:16 p-4"
>
<main
class="flex-row items-center justify-center mx-auto w-fit max-w-screen-sm gap-y-3"
>
<div hx-get="/#" swap="outerHTML">Loading...</div>
</main>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
package static
import (
_ "embed"
)
//go:embed index.html
var IndexHTML []byte
//go:embed sw.js
var WorkerJS []byte
+258
View File
@@ -0,0 +1,258 @@
// Cache names for different types of resources
const CACHE_NAMES = {
wasm: 'wasm-cache-v1',
static: 'static-cache-v1',
dynamic: 'dynamic-cache-v1'
};
// Import required scripts
importScripts(
"https://cdn.jsdelivr.net/gh/golang/go@go1.22.5/misc/wasm/wasm_exec.js",
"https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js",
);
// Initialize WASM HTTP listener
const wasmInstance = registerWasmHTTPListener("https://cdn.sonr.id/wasm/app.wasm");
// MessageChannel port for WASM communication
let wasmPort;
// Request queue for offline operations
let requestQueue = new Map();
// Setup message channel handler
self.addEventListener('message', async (event) => {
if (event.data.type === 'PORT_INITIALIZATION') {
wasmPort = event.data.port;
setupWasmCommunication();
}
});
function setupWasmCommunication() {
wasmPort.onmessage = async (event) => {
const { type, data } = event.data;
switch (type) {
case 'WASM_REQUEST':
handleWasmRequest(data);
break;
case 'SYNC_REQUEST':
processSyncQueue();
break;
}
};
// Notify that WASM is ready
wasmPort.postMessage({ type: 'WASM_READY' });
}
// Enhanced install event
self.addEventListener("install", (event) => {
event.waitUntil(
Promise.all([
skipWaiting(),
// Cache WASM binary and essential resources
caches.open(CACHE_NAMES.wasm).then(cache =>
cache.addAll([
'https://cdn.sonr.id/wasm/app.wasm',
'https://cdn.jsdelivr.net/gh/golang/go@go1.22.5/misc/wasm/wasm_exec.js'
])
)
])
);
});
// Enhanced activate event
self.addEventListener("activate", (event) => {
event.waitUntil(
Promise.all([
clients.claim(),
// Clean up old caches
caches.keys().then(keys =>
Promise.all(
keys.map(key => {
if (!Object.values(CACHE_NAMES).includes(key)) {
return caches.delete(key);
}
})
)
)
])
);
});
// Intercept fetch events
self.addEventListener('fetch', (event) => {
const request = event.request;
// Handle API requests differently from static resources
if (request.url.includes('/api/')) {
event.respondWith(handleApiRequest(request));
} else {
event.respondWith(handleStaticRequest(request));
}
});
async function handleApiRequest(request) {
try {
// Try to make the request
const response = await fetch(request.clone());
// If successful, pass through WASM handler
if (response.ok) {
return await processWasmResponse(request, response);
}
// If offline or failed, queue the request
await queueRequest(request);
// Return cached response if available
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Return offline response
return new Response(
JSON.stringify({ error: 'Currently offline' }),
{
status: 503,
headers: { 'Content-Type': 'application/json' }
}
);
} catch (error) {
await queueRequest(request);
return new Response(
JSON.stringify({ error: 'Request failed' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
}
async function handleStaticRequest(request) {
// Check cache first
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
try {
const response = await fetch(request);
// Cache successful responses
if (response.ok) {
const cache = await caches.open(CACHE_NAMES.static);
cache.put(request, response.clone());
}
return response;
} catch (error) {
// Return offline page for navigation requests
if (request.mode === 'navigate') {
return caches.match('/offline.html');
}
throw error;
}
}
async function processWasmResponse(request, response) {
// Clone the response before processing
const responseClone = response.clone();
try {
// Process through WASM
const processedResponse = await wasmInstance.processResponse(responseClone);
// Notify client through message channel
if (wasmPort) {
wasmPort.postMessage({
type: 'RESPONSE',
requestId: request.headers.get('X-Wasm-Request-ID'),
response: processedResponse
});
}
return processedResponse;
} catch (error) {
console.error('WASM processing error:', error);
return response;
}
}
async function queueRequest(request) {
const serializedRequest = await serializeRequest(request);
requestQueue.set(request.url, serializedRequest);
// Register for background sync
try {
await self.registration.sync.register('wasm-sync');
} catch (error) {
console.error('Sync registration failed:', error);
}
}
async function serializeRequest(request) {
const headers = {};
for (const [key, value] of request.headers.entries()) {
headers[key] = value;
}
return {
url: request.url,
method: request.method,
headers,
body: await request.text(),
timestamp: Date.now()
};
}
// Handle background sync
self.addEventListener('sync', (event) => {
if (event.tag === 'wasm-sync') {
event.waitUntil(processSyncQueue());
}
});
async function processSyncQueue() {
const requests = Array.from(requestQueue.values());
for (const serializedRequest of requests) {
try {
const response = await fetch(new Request(serializedRequest.url, {
method: serializedRequest.method,
headers: serializedRequest.headers,
body: serializedRequest.body
}));
if (response.ok) {
requestQueue.delete(serializedRequest.url);
// Notify client of successful sync
if (wasmPort) {
wasmPort.postMessage({
type: 'SYNC_COMPLETE',
url: serializedRequest.url
});
}
}
} catch (error) {
console.error('Sync failed for request:', error);
}
}
}
// Handle payment requests
self.addEventListener("canmakepayment", function (e) {
e.respondWith(Promise.resolve(true));
});
// Handle periodic sync if available
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'wasm-sync') {
event.waitUntil(processSyncQueue());
}
});
-31
View File
@@ -1,31 +0,0 @@
package types
import (
"github.com/ipfs/boxo/files"
"github.com/onsonr/sonr/pkg/motr"
"github.com/onsonr/sonr/pkg/motr/config"
)
type Vault struct {
FS files.Node
}
func NewVault(keyshareJSON string, adddress string, chainID string, schema *config.Schema) (*Vault, error) {
dwnCfg := &config.Config{
MotrKeyshare: keyshareJSON,
MotrAddress: adddress,
IpfsGatewayUrl: "https://ipfs.sonr.land",
SonrApiUrl: "https://api.sonr.land",
SonrRpcUrl: "https://rpc.sonr.land",
SonrChainId: chainID,
VaultSchema: schema,
}
fileMap, err := motr.NewVaultDirectory(dwnCfg)
if err != nil {
return nil, err
}
return &Vault{
FS: fileMap,
}, nil
}