mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 01:41:44 +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:
@@ -0,0 +1,117 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ucan-wg/go-ucan"
|
||||
)
|
||||
|
||||
// Capability hierarchy for smart account operations
|
||||
// ----------------------------------------------
|
||||
// OWNER
|
||||
// └─ OPERATOR
|
||||
// ├─ EXECUTE
|
||||
// ├─ PROPOSE
|
||||
// └─ SIGN
|
||||
// └─ SET_POLICY
|
||||
// └─ SET_THRESHOLD
|
||||
// └─ RECOVER
|
||||
// └─ SOCIAL
|
||||
|
||||
// Define capability hierarchy for smart account operations
|
||||
const (
|
||||
// Root capabilities
|
||||
CAP_OWNER = "OWNER" // Full account control
|
||||
CAP_OPERATOR = "OPERATOR" // Can perform operations
|
||||
CAP_OBSERVER = "OBSERVER" // Can view account state
|
||||
|
||||
// Operation capabilities
|
||||
CAP_EXECUTE = "EXECUTE" // Can execute transactions
|
||||
CAP_PROPOSE = "PROPOSE" // Can propose transactions
|
||||
CAP_SIGN = "SIGN" // Can sign transactions
|
||||
|
||||
// Policy capabilities
|
||||
CAP_SET_POLICY = "SET_POLICY" // Can modify account policies
|
||||
CAP_SET_THRESHOLD = "SET_THRESHOLD" // Can modify signing threshold
|
||||
|
||||
// Recovery capabilities
|
||||
CAP_RECOVER = "RECOVER" // Can initiate recovery
|
||||
CAP_SOCIAL = "SOCIAL" // Can act as social recovery
|
||||
)
|
||||
|
||||
// SmartAccountCapabilities defines the capability hierarchy
|
||||
func NewSmartAccountCapabilities() ucan.NestedCapabilities {
|
||||
return ucan.NewNestedCapabilities(
|
||||
CAP_OWNER,
|
||||
CAP_OPERATOR,
|
||||
CAP_OBSERVER,
|
||||
CAP_EXECUTE,
|
||||
CAP_PROPOSE,
|
||||
CAP_SIGN,
|
||||
CAP_SET_POLICY,
|
||||
CAP_SET_THRESHOLD,
|
||||
CAP_RECOVER,
|
||||
CAP_SOCIAL,
|
||||
)
|
||||
}
|
||||
|
||||
// Resource types for smart account operations
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
RES_ACCOUNT = "account"
|
||||
RES_TRANSACTION = "tx"
|
||||
RES_POLICY = "policy"
|
||||
RES_RECOVERY = "recovery"
|
||||
)
|
||||
|
||||
// NewSmartAccountResource creates a new resource identifier
|
||||
func NewSmartAccountResource(resType ResourceType, path string) ucan.Resource {
|
||||
return ucan.NewStringLengthResource(string(resType), path)
|
||||
}
|
||||
|
||||
// CreateSmartAccountAttenuations creates default attenuations for a smart account
|
||||
func CreateSmartAccountAttenuations(
|
||||
caps ucan.NestedCapabilities,
|
||||
accountAddr string,
|
||||
) ucan.Attenuations {
|
||||
return ucan.Attenuations{
|
||||
// Owner capabilities
|
||||
{caps.Cap(CAP_OWNER), NewSmartAccountResource(RES_ACCOUNT, accountAddr)},
|
||||
|
||||
// Operation capabilities
|
||||
{caps.Cap(CAP_EXECUTE), NewSmartAccountResource(RES_TRANSACTION, fmt.Sprintf("%s:*", accountAddr))},
|
||||
{caps.Cap(CAP_PROPOSE), NewSmartAccountResource(RES_TRANSACTION, fmt.Sprintf("%s:*", accountAddr))},
|
||||
{caps.Cap(CAP_SIGN), NewSmartAccountResource(RES_TRANSACTION, fmt.Sprintf("%s:*", accountAddr))},
|
||||
|
||||
// Policy capabilities
|
||||
{caps.Cap(CAP_SET_POLICY), NewSmartAccountResource(RES_POLICY, fmt.Sprintf("%s:*", accountAddr))},
|
||||
{caps.Cap(CAP_SET_THRESHOLD), NewSmartAccountResource(RES_POLICY, fmt.Sprintf("%s:threshold", accountAddr))},
|
||||
}
|
||||
}
|
||||
|
||||
// Policy represents smart account execution policies
|
||||
type PolicyType string
|
||||
|
||||
const (
|
||||
POLICY_THRESHOLD = "threshold"
|
||||
POLICY_TIMELOCK = "timelock"
|
||||
POLICY_WHITELIST = "whitelist"
|
||||
)
|
||||
|
||||
// CreatePolicyAttenuation creates attenuations for policy management
|
||||
func CreatePolicyAttenuation(
|
||||
caps ucan.NestedCapabilities,
|
||||
accountAddr string,
|
||||
policyType PolicyType,
|
||||
) ucan.Attenuations {
|
||||
return ucan.Attenuations{
|
||||
{
|
||||
caps.Cap(CAP_SET_POLICY),
|
||||
NewSmartAccountResource(
|
||||
RES_POLICY,
|
||||
fmt.Sprintf("%s:%s", accountAddr, policyType),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
// MPCSigningMethod implements the SigningMethod interface for MPC-based signing
|
||||
type MPCSigningMethod struct {
|
||||
Name string
|
||||
ks keyshareSource
|
||||
}
|
||||
|
||||
// newMPCSigningMethod creates a new MPC signing method with the given keyshare source
|
||||
func newMPCSigningMethod(name string, ks keyshareSource) *MPCSigningMethod {
|
||||
return &MPCSigningMethod{
|
||||
Name: name,
|
||||
ks: ks,
|
||||
}
|
||||
}
|
||||
|
||||
// Alg returns the signing method's name
|
||||
func (m *MPCSigningMethod) Alg() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
// Verify verifies the signature using the MPC public key
|
||||
func (m *MPCSigningMethod) Verify(signingString, signature string, key interface{}) error {
|
||||
// Decode the signature
|
||||
sig, err := base64.RawURLEncoding.DecodeString(signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Hash the signing string
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
digest := hasher.Sum(nil)
|
||||
|
||||
// Verify using the keyshare's public key
|
||||
valid, err := VerifySignature(m.ks.valShare.PublicKey, digest, sig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify signature: %w", err)
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign signs the data using MPC
|
||||
func (m *MPCSigningMethod) Sign(signingString string, key interface{}) (string, error) {
|
||||
// Hash the signing string
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
digest := hasher.Sum(nil)
|
||||
|
||||
// Create signing functions
|
||||
signFunc, err := m.ks.userShare.SignFunc(digest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create sign function: %w", err)
|
||||
}
|
||||
|
||||
valSignFunc, err := m.ks.valShare.SignFunc(digest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create validator sign function: %w", err)
|
||||
}
|
||||
|
||||
// Run the signing protocol
|
||||
sig, err := RunSignProtocol(valSignFunc, signFunc)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to run sign protocol: %w", err)
|
||||
}
|
||||
|
||||
// Serialize the signature
|
||||
sigBytes, err := SerializeSignature(sig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to serialize signature: %w", err)
|
||||
}
|
||||
|
||||
// Encode the signature
|
||||
encoded := base64.RawURLEncoding.EncodeToString(sigBytes)
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register the MPC signing method
|
||||
jwt.RegisterSigningMethod("MPC256", func() jwt.SigningMethod {
|
||||
return &MPCSigningMethod{
|
||||
Name: "MPC256",
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/crypto/core/curves"
|
||||
"github.com/onsonr/sonr/pkg/crypto/core/protocol"
|
||||
"github.com/onsonr/sonr/pkg/crypto/tecdsa/dklsv1"
|
||||
)
|
||||
|
||||
// NewKeyshareSource generates a new MPC keyshare
|
||||
func NewKeyshareSource() (KeyshareSource, error) {
|
||||
curve := curves.K256()
|
||||
valKs := dklsv1.NewAliceDkg(curve, protocol.Version1)
|
||||
userKs := dklsv1.NewBobDkg(curve, protocol.Version1)
|
||||
aErr, bErr := runIteratedProtocol(userKs, valKs)
|
||||
if err := checkIteratedErrors(aErr, bErr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valRes, err := valKs.Result(protocol.Version1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valShare, err := NewValKeyshare(valRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userRes, err := userKs.Result(protocol.Version1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userShare, err := NewUserKeyshare(userRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createKeyshareSource(valShare, userShare)
|
||||
}
|
||||
|
||||
// RunSignProtocol runs the MPC signing protocol
|
||||
func RunSignProtocol(signFuncVal SignFunc, signFuncUser SignFunc) (Signature, error) {
|
||||
aErr, bErr := runIteratedProtocol(signFuncVal, signFuncUser)
|
||||
if err := checkIteratedErrors(aErr, bErr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := signFuncUser.Result(protocol.Version1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dklsv1.DecodeSignature(out)
|
||||
}
|
||||
|
||||
// RunRefreshProtocol runs the MPC refresh protocol
|
||||
func RunRefreshProtocol(refreshFuncVal RefreshFunc, refreshFuncUser RefreshFunc) (KeyshareSource, error) {
|
||||
aErr, bErr := runIteratedProtocol(refreshFuncVal, refreshFuncUser)
|
||||
if err := checkIteratedErrors(aErr, bErr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valRefreshResult, err := refreshFuncVal.Result(protocol.Version1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valShare, err := NewValKeyshare(valRefreshResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userRefreshResult, err := refreshFuncUser.Result(protocol.Version1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userShare, err := NewUserKeyshare(userRefreshResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createKeyshareSource(valShare, userShare)
|
||||
}
|
||||
|
||||
// SerializeSecp256k1Signature serializes an ECDSA signature into a byte slice
|
||||
func SerializeSignature(sig Signature) ([]byte, error) {
|
||||
rBytes := sig.R.Bytes()
|
||||
sBytes := sig.S.Bytes()
|
||||
|
||||
sigBytes := make([]byte, 66) // V (1 byte) + R (32 bytes) + S (32 bytes)
|
||||
sigBytes[0] = byte(sig.V)
|
||||
copy(sigBytes[33-len(rBytes):33], rBytes)
|
||||
copy(sigBytes[66-len(sBytes):66], sBytes)
|
||||
return sigBytes, nil
|
||||
}
|
||||
|
||||
// DeserializeSecp256k1Signature deserializes an ECDSA signature from a byte slice
|
||||
func DeserializeSignature(sigBytes []byte) (Signature, error) {
|
||||
if len(sigBytes) != 66 {
|
||||
return nil, errors.New("malformed signature: not the correct size")
|
||||
}
|
||||
sig := &curves.EcdsaSignature{
|
||||
V: int(sigBytes[0]),
|
||||
R: new(big.Int).SetBytes(sigBytes[1:33]),
|
||||
S: new(big.Int).SetBytes(sigBytes[33:66]),
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// VerifyMPCSignature verifies an MPC signature
|
||||
func VerifyMPCSignature(sig Signature, msg []byte, publicKey *ecdsa.PublicKey) bool {
|
||||
return ecdsa.Verify(publicKey, msg, sig.R, sig.S)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package mpc_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/crypto/mpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKeyshareGeneration(t *testing.T) {
|
||||
// Test data
|
||||
testData := []byte("hello world")
|
||||
|
||||
// Generate keyshares
|
||||
src, err := mpc.NewKeyshareSource()
|
||||
require.NoError(t, err)
|
||||
src.Address()
|
||||
|
||||
// Test signing with keyshares
|
||||
sig, err := src.SignData(testData)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sig)
|
||||
|
||||
// Verify signature
|
||||
valid, err := src.VerifyData(testData, sig)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, valid)
|
||||
tk, err := src.DefaultOriginToken()
|
||||
require.NoError(t, err)
|
||||
cid, err := tk.CID()
|
||||
require.NoError(t, err)
|
||||
fmt.Println(cid)
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/crypto/core/curves"
|
||||
"github.com/onsonr/sonr/pkg/crypto/core/protocol"
|
||||
"github.com/onsonr/sonr/pkg/crypto/tecdsa/dklsv1"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var ErrInvalidKeyshareRole = errors.New("invalid keyshare role")
|
||||
|
||||
type Role int
|
||||
|
||||
const (
|
||||
RoleUnknown Role = iota
|
||||
RoleUser
|
||||
RoleValidator
|
||||
)
|
||||
|
||||
func (r Role) IsUser() bool {
|
||||
return r == RoleUser
|
||||
}
|
||||
|
||||
func (r Role) IsValidator() bool {
|
||||
return r == RoleValidator
|
||||
}
|
||||
|
||||
// Message is the protocol.Message that is used for MPC
|
||||
type Message *protocol.Message
|
||||
|
||||
type PublicKey *ecdsa.PublicKey
|
||||
|
||||
type Signature *curves.EcdsaSignature
|
||||
|
||||
// RefreshFunc is the type for the refresh function
|
||||
type RefreshFunc interface {
|
||||
protocol.Iterator
|
||||
}
|
||||
|
||||
// SignFunc is the type for the sign function
|
||||
type SignFunc interface {
|
||||
protocol.Iterator
|
||||
}
|
||||
|
||||
type ValKeyshare struct {
|
||||
Message Message `json:"message"`
|
||||
Role int `json:"role"` // 1 for validator, 2 for user
|
||||
PublicKey []byte `json:"public-key"`
|
||||
}
|
||||
|
||||
func NewValKeyshare(msg Message) (*ValKeyshare, error) {
|
||||
valShare, err := dklsv1.DecodeAliceDkgResult(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ValKeyshare{
|
||||
Message: msg,
|
||||
Role: 1,
|
||||
PublicKey: valShare.PublicKey.ToAffineUncompressed(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v ValKeyshare) GetPayloads() map[string][]byte {
|
||||
return v.Message.Payloads
|
||||
}
|
||||
|
||||
func (v ValKeyshare) GetMetadata() map[string]string {
|
||||
return v.Message.Metadata
|
||||
}
|
||||
|
||||
func (v ValKeyshare) GetPublicKey() []byte {
|
||||
return v.PublicKey
|
||||
}
|
||||
|
||||
func (v ValKeyshare) GetProtocol() string {
|
||||
return v.Message.Protocol
|
||||
}
|
||||
|
||||
func (v ValKeyshare) GetRole() int32 {
|
||||
return int32(v.Role)
|
||||
}
|
||||
|
||||
func (v ValKeyshare) GetVersion() uint32 {
|
||||
return uint32(v.Message.Version)
|
||||
}
|
||||
|
||||
func (k ValKeyshare) ECDSAPublicKey() (*ecdsa.PublicKey, error) {
|
||||
return ComputeEcdsaPublicKey(k.PublicKey)
|
||||
}
|
||||
|
||||
func (k ValKeyshare) ExtractMessage() *protocol.Message {
|
||||
return &protocol.Message{
|
||||
Payloads: k.GetPayloads(),
|
||||
Metadata: k.GetMetadata(),
|
||||
Protocol: k.GetProtocol(),
|
||||
Version: uint(k.GetVersion()),
|
||||
}
|
||||
}
|
||||
|
||||
func (k ValKeyshare) RefreshFunc() (RefreshFunc, error) {
|
||||
curve := curves.K256()
|
||||
return dklsv1.NewAliceRefresh(curve, k.ExtractMessage(), protocol.Version1)
|
||||
}
|
||||
|
||||
func (k ValKeyshare) SignFunc(msg []byte) (SignFunc, error) {
|
||||
curve := curves.K256()
|
||||
return dklsv1.NewAliceSign(curve, sha3.New256(), msg, k.ExtractMessage(), protocol.Version1)
|
||||
}
|
||||
|
||||
func (v ValKeyshare) Marshal() (string, error) {
|
||||
jsonBytes, err := json.Marshal(v)
|
||||
return string(jsonBytes), err
|
||||
}
|
||||
|
||||
func (v ValKeyshare) Unmarshal(data string) error {
|
||||
return json.Unmarshal([]byte(data), &v)
|
||||
}
|
||||
|
||||
type UserKeyshare struct {
|
||||
Message Message `json:"message"` // BobOutput
|
||||
Role int `json:"role"` // 2 for user, 1 for validator
|
||||
PublicKey []byte `json:"public-key"`
|
||||
}
|
||||
|
||||
func NewUserKeyshare(msg Message) (*UserKeyshare, error) {
|
||||
out, err := dklsv1.DecodeBobDkgResult(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UserKeyshare{
|
||||
Message: msg,
|
||||
Role: 2,
|
||||
PublicKey: out.PublicKey.ToAffineUncompressed(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u UserKeyshare) GetPayloads() map[string][]byte {
|
||||
return u.Message.Payloads
|
||||
}
|
||||
|
||||
func (u UserKeyshare) GetMetadata() map[string]string {
|
||||
return u.Message.Metadata
|
||||
}
|
||||
|
||||
func (u UserKeyshare) GetPublicKey() []byte {
|
||||
return u.PublicKey
|
||||
}
|
||||
|
||||
func (u UserKeyshare) GetProtocol() string {
|
||||
return u.Message.Protocol
|
||||
}
|
||||
|
||||
func (u UserKeyshare) GetRole() int32 {
|
||||
return int32(u.Role)
|
||||
}
|
||||
|
||||
func (u UserKeyshare) GetVersion() uint32 {
|
||||
return uint32(u.Message.Version)
|
||||
}
|
||||
|
||||
func (k UserKeyshare) ECDSAPublicKey() (*ecdsa.PublicKey, error) {
|
||||
return ComputeEcdsaPublicKey(k.PublicKey)
|
||||
}
|
||||
|
||||
func (k UserKeyshare) ExtractMessage() *protocol.Message {
|
||||
return &protocol.Message{
|
||||
Payloads: k.GetPayloads(),
|
||||
Metadata: k.GetMetadata(),
|
||||
Protocol: k.GetProtocol(),
|
||||
Version: uint(k.GetVersion()),
|
||||
}
|
||||
}
|
||||
|
||||
func (k UserKeyshare) RefreshFunc() (RefreshFunc, error) {
|
||||
curve := curves.K256()
|
||||
return dklsv1.NewBobRefresh(curve, k.ExtractMessage(), protocol.Version1)
|
||||
}
|
||||
|
||||
func (k UserKeyshare) SignFunc(msg []byte) (SignFunc, error) {
|
||||
curve := curves.K256()
|
||||
return dklsv1.NewBobSign(curve, sha3.New256(), msg, k.ExtractMessage(), protocol.Version1)
|
||||
}
|
||||
|
||||
func (u UserKeyshare) Marshal() (string, error) {
|
||||
jsonBytes, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(jsonBytes), nil
|
||||
}
|
||||
|
||||
func (u UserKeyshare) Unmarshal(data string) error {
|
||||
return json.Unmarshal([]byte(data), &u)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ucan-wg/go-ucan"
|
||||
)
|
||||
|
||||
type KeyshareSource interface {
|
||||
ucan.Source
|
||||
|
||||
Address() string
|
||||
Issuer() string
|
||||
DefaultOriginToken() (*Token, error)
|
||||
PublicKey() []byte
|
||||
TokenParser() *ucan.TokenParser
|
||||
SignData(data []byte) ([]byte, error)
|
||||
VerifyData(data []byte, sig []byte) (bool, error)
|
||||
}
|
||||
|
||||
func createKeyshareSource(val *ValKeyshare, user *UserKeyshare) (KeyshareSource, error) {
|
||||
iss, addr, err := ComputeIssuerDID(val.GetPublicKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keyshareSource{
|
||||
userShare: user,
|
||||
valShare: val,
|
||||
addr: addr,
|
||||
issuerDID: iss,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Address returns the address of the keyshare
|
||||
func (k keyshareSource) Address() string {
|
||||
return k.addr
|
||||
}
|
||||
|
||||
// Issuer returns the DID of the issuer of the keyshare
|
||||
func (k keyshareSource) Issuer() string {
|
||||
return k.issuerDID
|
||||
}
|
||||
|
||||
// PublicKey returns the public key of the keyshare
|
||||
func (k keyshareSource) PublicKey() []byte {
|
||||
return k.valShare.PublicKey
|
||||
}
|
||||
|
||||
// DefaultOriginToken returns a default token with the keyshare's issuer as the audience
|
||||
func (k keyshareSource) DefaultOriginToken() (*Token, error) {
|
||||
caps := NewSmartAccountCapabilities()
|
||||
att := CreateSmartAccountAttenuations(caps, k.addr)
|
||||
zero := time.Time{}
|
||||
return k.NewOriginToken(k.issuerDID, att, nil, zero, zero)
|
||||
}
|
||||
|
||||
// TokenParser returns a token parser that can be used to parse tokens
|
||||
func (k keyshareSource) TokenParser() *ucan.TokenParser {
|
||||
caps := NewSmartAccountCapabilities()
|
||||
ac := func(m map[string]interface{}) (ucan.Attenuation, error) {
|
||||
var (
|
||||
cap string
|
||||
rsc ucan.Resource
|
||||
)
|
||||
for key, vali := range m {
|
||||
val, ok := vali.(string)
|
||||
if !ok {
|
||||
return ucan.Attenuation{}, fmt.Errorf(`expected attenuation value to be a string`)
|
||||
}
|
||||
|
||||
if key == ucan.CapKey {
|
||||
cap = val
|
||||
} else {
|
||||
rsc = ucan.NewStringLengthResource(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
return ucan.Attenuation{
|
||||
Rsc: rsc,
|
||||
Cap: caps.Cap(cap),
|
||||
}, nil
|
||||
}
|
||||
|
||||
store := ucan.NewMemTokenStore()
|
||||
return ucan.NewTokenParser(ac, ucan.StringDIDPubKeyResolver{}, store.(ucan.CIDBytesResolver))
|
||||
}
|
||||
|
||||
func (k keyshareSource) SignData(data []byte) ([]byte, error) {
|
||||
|
||||
// Create signing functions
|
||||
signFunc, err := k.userShare.SignFunc(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create sign function: %w", err)
|
||||
}
|
||||
|
||||
valSignFunc, err := k.valShare.SignFunc(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create validator sign function: %w", err)
|
||||
}
|
||||
|
||||
// Run the signing protocol
|
||||
sig, err := RunSignProtocol(valSignFunc, signFunc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run sign protocol: %w", err)
|
||||
}
|
||||
return SerializeSignature(sig)
|
||||
}
|
||||
|
||||
func (k keyshareSource) VerifyData(data []byte, sig []byte) (bool, error) {
|
||||
return VerifySignature(k.userShare.PublicKey, data, sig)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/ucan-wg/go-ucan"
|
||||
)
|
||||
|
||||
type (
|
||||
Token = ucan.Token
|
||||
Claims = ucan.Claims
|
||||
Proof = ucan.Proof
|
||||
|
||||
Attenuations = ucan.Attenuations
|
||||
Fact = ucan.Fact
|
||||
)
|
||||
|
||||
var (
|
||||
UCANVersion = ucan.UCANVersion
|
||||
UCANVersionKey = ucan.UCANVersionKey
|
||||
PrfKey = ucan.PrfKey
|
||||
FctKey = ucan.FctKey
|
||||
AttKey = ucan.AttKey
|
||||
CapKey = ucan.CapKey
|
||||
)
|
||||
|
||||
type keyshareSource struct {
|
||||
userShare *UserKeyshare
|
||||
valShare *ValKeyshare
|
||||
|
||||
addr string
|
||||
issuerDID string
|
||||
}
|
||||
|
||||
func (k keyshareSource) NewOriginToken(audienceDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*ucan.Token, error) {
|
||||
return k.newToken(audienceDID, nil, att, fct, notBefore, expires)
|
||||
}
|
||||
|
||||
func (k keyshareSource) NewAttenuatedToken(parent *Token, audienceDID string, att ucan.Attenuations, fct []ucan.Fact, nbf, exp time.Time) (*Token, error) {
|
||||
if !parent.Attenuations.Contains(att) {
|
||||
return nil, fmt.Errorf("scope of ucan attenuations must be less than it's parent")
|
||||
}
|
||||
return k.newToken(audienceDID, append(parent.Proofs, Proof(parent.Raw)), att, fct, nbf, exp)
|
||||
}
|
||||
|
||||
func (k keyshareSource) newToken(audienceDID string, prf []Proof, att Attenuations, fct []Fact, nbf, exp time.Time) (*ucan.Token, error) {
|
||||
t := jwt.New(newMPCSigningMethod("MPC256", k))
|
||||
|
||||
// if _, err := did.Parse(audienceDID); err != nil {
|
||||
// return nil, fmt.Errorf("invalid audience DID: %w", err)
|
||||
// }
|
||||
|
||||
t.Header[UCANVersionKey] = UCANVersion
|
||||
|
||||
var (
|
||||
nbfUnix int64
|
||||
expUnix int64
|
||||
)
|
||||
|
||||
if !nbf.IsZero() {
|
||||
nbfUnix = nbf.Unix()
|
||||
}
|
||||
if !exp.IsZero() {
|
||||
expUnix = exp.Unix()
|
||||
}
|
||||
|
||||
// set our claims
|
||||
t.Claims = &Claims{
|
||||
StandardClaims: &jwt.StandardClaims{
|
||||
Issuer: k.issuerDID,
|
||||
Audience: audienceDID,
|
||||
NotBefore: nbfUnix,
|
||||
// set the expire time
|
||||
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
|
||||
ExpiresAt: expUnix,
|
||||
},
|
||||
Attenuations: att,
|
||||
Facts: fct,
|
||||
Proofs: prf,
|
||||
}
|
||||
|
||||
raw, err := t.SignedString(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Token{
|
||||
Raw: raw,
|
||||
Attenuations: att,
|
||||
Facts: fct,
|
||||
Proofs: prf,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package mpc
|
||||
|
||||
import (
|
||||
genericecdsa "crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/bech32"
|
||||
"github.com/onsonr/sonr/pkg/crypto/core/curves"
|
||||
"github.com/onsonr/sonr/pkg/crypto/core/protocol"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func ComputeIssuerDID(pk []byte) (string, string, error) {
|
||||
addr, err := ComputeSonrAddr(pk)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return fmt.Sprintf("did:sonr:%s", addr), addr, nil
|
||||
}
|
||||
|
||||
func ComputeSonrAddr(pk []byte) (string, error) {
|
||||
sonrAddr, err := bech32.ConvertAndEncode("idx", pk)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sonrAddr, nil
|
||||
}
|
||||
|
||||
// For DKG bob starts first. For refresh and sign, Alice starts first.
|
||||
func runIteratedProtocol(firstParty protocol.Iterator, secondParty protocol.Iterator) (error, error) {
|
||||
var (
|
||||
message *protocol.Message
|
||||
aErr error
|
||||
bErr error
|
||||
)
|
||||
|
||||
for aErr != protocol.ErrProtocolFinished || bErr != protocol.ErrProtocolFinished {
|
||||
// Crank each protocol forward one iteration
|
||||
message, bErr = firstParty.Next(message)
|
||||
if bErr != nil && bErr != protocol.ErrProtocolFinished {
|
||||
return nil, bErr
|
||||
}
|
||||
|
||||
message, aErr = secondParty.Next(message)
|
||||
if aErr != nil && aErr != protocol.ErrProtocolFinished {
|
||||
return aErr, nil
|
||||
}
|
||||
}
|
||||
return aErr, bErr
|
||||
}
|
||||
|
||||
// ComputeEcPoint builds an elliptic curve point from a compressed byte slice
|
||||
func ComputeEcPoint(pubKey []byte) (*curves.EcPoint, error) {
|
||||
crv := curves.K256()
|
||||
x := new(big.Int).SetBytes(pubKey[1:33])
|
||||
y := new(big.Int).SetBytes(pubKey[33:])
|
||||
ecCurve, err := crv.ToEllipticCurve()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting curve: %v", err)
|
||||
}
|
||||
return &curves.EcPoint{X: x, Y: y, Curve: ecCurve}, nil
|
||||
}
|
||||
|
||||
func ComputeEcdsaPublicKey(pubKey []byte) (*genericecdsa.PublicKey, error) {
|
||||
pk, err := ComputeEcPoint(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &genericecdsa.PublicKey{
|
||||
Curve: pk.Curve,
|
||||
X: pk.X,
|
||||
Y: pk.Y,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifySignature verifies the signature of a message
|
||||
func VerifySignature(pk []byte, msg []byte, sig []byte) (bool, error) {
|
||||
pp, err := ComputeEcPoint(pk)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
sigEd, err := DeserializeSignature(sig)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
hash := sha3.New256()
|
||||
_, err = hash.Write(msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
digest := hash.Sum(nil)
|
||||
return curves.VerifyEcdsa(pp, digest[:], sigEd), nil
|
||||
}
|
||||
|
||||
func checkIteratedErrors(aErr, bErr error) error {
|
||||
if aErr == protocol.ErrProtocolFinished && bErr == protocol.ErrProtocolFinished {
|
||||
return nil
|
||||
}
|
||||
if aErr != protocol.ErrProtocolFinished {
|
||||
return aErr
|
||||
}
|
||||
if bErr != protocol.ErrProtocolFinished {
|
||||
return bErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user