mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
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:
+20
-39
@@ -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 +0,0 @@
|
||||
package signer
|
||||
@@ -1 +0,0 @@
|
||||
package signer
|
||||
@@ -1 +0,0 @@
|
||||
package wallet
|
||||
@@ -1 +0,0 @@
|
||||
package wallet
|
||||
@@ -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
|
||||
@@ -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
|
||||
// }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -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,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"
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,11 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed index.html
|
||||
var IndexHTML []byte
|
||||
|
||||
//go:embed sw.js
|
||||
var WorkerJS []byte
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user