refactor(enclave): migrate to enclave signing with MPC
This commit is contained in:
@@ -52,7 +52,7 @@ func ping() int32 {
|
|||||||
|
|
||||||
//go:wasmexport generate
|
//go:wasmexport generate
|
||||||
func generate() int32 {
|
func generate() int32 {
|
||||||
pdk.Log(pdk.LogInfo, "generate: starting database initialization")
|
pdk.Log(pdk.LogInfo, "generate: starting")
|
||||||
|
|
||||||
var input types.GenerateInput
|
var input types.GenerateInput
|
||||||
if err := pdk.InputJSON(&input); err != nil {
|
if err := pdk.InputJSON(&input); err != nil {
|
||||||
@@ -71,44 +71,32 @@ func generate() int32 {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, "generate: opening keybase")
|
result, err := initializeWithMPC(credentialBytes)
|
||||||
kb, err := keybase.Open()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pdk.SetError(fmt.Errorf("generate: open database: %w", err))
|
pdk.SetError(fmt.Errorf("generate: %w", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, "generate: initializing DID")
|
|
||||||
ctx := context.Background()
|
|
||||||
did, err := kb.Initialize(ctx, credentialBytes)
|
|
||||||
if err != nil {
|
|
||||||
pdk.SetError(fmt.Errorf("generate: initialize DID: %w", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("generate: DID created: %s", did))
|
|
||||||
|
|
||||||
state.SetInitialized(true)
|
state.SetInitialized(true)
|
||||||
state.SetDID(did)
|
state.SetDID(result.DID)
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, "generate: serializing database")
|
|
||||||
dbBytes, err := serializeDatabase()
|
dbBytes, err := serializeDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pdk.SetError(fmt.Errorf("generate: failed to serialize database: %w", err))
|
pdk.SetError(fmt.Errorf("generate: serialize: %w", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
output := types.GenerateOutput{
|
output := types.GenerateOutput{
|
||||||
DID: did,
|
DID: result.DID,
|
||||||
Database: dbBytes,
|
Database: dbBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pdk.OutputJSON(output); err != nil {
|
if err := pdk.OutputJSON(output); err != nil {
|
||||||
pdk.SetError(fmt.Errorf("generate: failed to output result: %w", err))
|
pdk.SetError(fmt.Errorf("generate: output: %w", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("generate: created DID %s (no MPC)", did))
|
pdk.Log(pdk.LogInfo, fmt.Sprintf("generate: created DID %s with enclave %s", result.DID, result.EnclaveID))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,28 +250,21 @@ type initResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initializeWithMPC(credentialBytes []byte) (*initResult, error) {
|
func initializeWithMPC(credentialBytes []byte) (*initResult, error) {
|
||||||
pdk.Log(pdk.LogInfo, "initializeWithMPC: step 1 - opening database")
|
|
||||||
kb, err := keybase.Open()
|
kb, err := keybase.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("open database: %w", err)
|
return nil, fmt.Errorf("open database: %w", err)
|
||||||
}
|
}
|
||||||
pdk.Log(pdk.LogInfo, "initializeWithMPC: step 2 - database opened")
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
pdk.Log(pdk.LogInfo, "initializeWithMPC: step 3 - initializing DID")
|
|
||||||
did, err := kb.Initialize(ctx, credentialBytes)
|
did, err := kb.Initialize(ctx, credentialBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("initialize: %w", err)
|
return nil, fmt.Errorf("initialize DID: %w", err)
|
||||||
}
|
}
|
||||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeWithMPC: step 4 - DID initialized: %s", did))
|
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, "initializeWithMPC: step 5 - generating simple enclave")
|
|
||||||
simpleEnc, err := mpc.NewSimpleEnclave()
|
simpleEnc, err := mpc.NewSimpleEnclave()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pdk.Log(pdk.LogError, fmt.Sprintf("initializeWithMPC: enclave generation failed: %v", err))
|
|
||||||
return nil, fmt.Errorf("generate enclave: %w", err)
|
return nil, fmt.Errorf("generate enclave: %w", err)
|
||||||
}
|
}
|
||||||
pdk.Log(pdk.LogInfo, "initializeWithMPC: step 6 - enclave generated")
|
|
||||||
|
|
||||||
enclaveID := fmt.Sprintf("enc_%x", credentialBytes[:8])
|
enclaveID := fmt.Sprintf("enc_%x", credentialBytes[:8])
|
||||||
|
|
||||||
@@ -305,11 +286,9 @@ func initializeWithMPC(credentialBytes []byte) (*initResult, error) {
|
|||||||
return nil, fmt.Errorf("store enclave: %w", err)
|
return nil, fmt.Errorf("store enclave: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeWithMPC: stored enclave %s", enclaveID))
|
|
||||||
|
|
||||||
accounts, err := createDefaultAccounts(ctx, am, enc.ID, simpleEnc.PubKeyBytes())
|
accounts, err := createDefaultAccounts(ctx, am, enc.ID, simpleEnc.PubKeyBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pdk.Log(pdk.LogWarn, fmt.Sprintf("initializeWithMPC: failed to create accounts: %s", err))
|
pdk.Log(pdk.LogWarn, fmt.Sprintf("createDefaultAccounts: %s", err))
|
||||||
accounts = []types.AccountInfo{}
|
accounts = []types.AccountInfo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +301,7 @@ func initializeWithMPC(credentialBytes []byte) (*initResult, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultAccounts(ctx context.Context, am *keybase.ActionManager, enclaveID int64, pubKeyBytes []byte) ([]types.AccountInfo, error) {
|
func createDefaultAccounts(ctx context.Context, am *keybase.ActionManager, enclaveID int64, pubKeyBytes []byte) ([]types.AccountInfo, error) {
|
||||||
chains := []string{"bitcoin", "ethereum", "sonr"}
|
chains := []string{"sonr", "ethereum", "bitcoin"}
|
||||||
derivedAccounts, err := bip44.DeriveAccounts(pubKeyBytes, chains)
|
derivedAccounts, err := bip44.DeriveAccounts(pubKeyBytes, chains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("derive accounts: %w", err)
|
return nil, fmt.Errorf("derive accounts: %w", err)
|
||||||
@@ -346,7 +325,6 @@ func createDefaultAccounts(ctx context.Context, am *keybase.ActionManager, encla
|
|||||||
IsDefault: isDefault,
|
IsDefault: isDefault,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pdk.Log(pdk.LogWarn, fmt.Sprintf("createDefaultAccounts: failed for %s: %s", derived.ChainID, err))
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,15 +516,9 @@ func matchResource(pattern, resource string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executeAction(params *types.FilterParams) (json.RawMessage, error) {
|
func executeAction(params *types.FilterParams) (json.RawMessage, error) {
|
||||||
if params.Resource == "accounts" {
|
if params.Resource == "accounts" && params.Action == "balances" {
|
||||||
switch params.Action {
|
return fetchAccountBalances(params.Subject)
|
||||||
case "balances":
|
|
||||||
return fetchAccountBalances(params.Subject)
|
|
||||||
case "sign":
|
|
||||||
return json.Marshal(map[string]string{"signature": "placeholder"})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keybase.Exec(context.Background(), params.Resource, params.Action, params.Subject)
|
return keybase.Exec(context.Background(), params.Resource, params.Action, params.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package mpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/sonr-io/crypto/core/curves"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetECDSAPoint(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 DeserializeSignature(sigBytes []byte) (*curves.EcdsaSignature, error) {
|
|
||||||
if len(sigBytes) != 64 {
|
|
||||||
return nil, fmt.Errorf("invalid signature length: expected 64 bytes, got %d", len(sigBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
r := new(big.Int).SetBytes(sigBytes[:32])
|
|
||||||
s := new(big.Int).SetBytes(sigBytes[32:])
|
|
||||||
|
|
||||||
return &curves.EcdsaSignature{
|
|
||||||
R: r,
|
|
||||||
S: s,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -2,28 +2,106 @@ package mpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func VerifyWithPubKey(pubKeyCompressed []byte, data []byte, sig []byte) (bool, error) {
|
func VerifyWithPubKey(pubKey []byte, data []byte, sig []byte) (bool, error) {
|
||||||
edSig, err := DeserializeSignature(sig)
|
if len(sig) != 64 {
|
||||||
if err != nil {
|
return false, fmt.Errorf("invalid signature length: expected 64, got %d", len(sig))
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
ePub, err := GetECDSAPoint(pubKeyCompressed)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
pk := &ecdsa.PublicKey{
|
|
||||||
Curve: ePub.Curve,
|
|
||||||
X: ePub.X,
|
|
||||||
Y: ePub.Y,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the message using SHA3-256
|
pk, err := parsePublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(big.Int).SetBytes(sig[:32])
|
||||||
|
s := new(big.Int).SetBytes(sig[32:])
|
||||||
|
|
||||||
hash := sha3.New256()
|
hash := sha3.New256()
|
||||||
hash.Write(data)
|
hash.Write(data)
|
||||||
digest := hash.Sum(nil)
|
digest := hash.Sum(nil)
|
||||||
return ecdsa.Verify(pk, digest, edSig.R, edSig.S), nil
|
|
||||||
|
return ecdsa.Verify(pk, digest, r, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePublicKey(pubKey []byte) (*ecdsa.PublicKey, error) {
|
||||||
|
curve := elliptic.P256()
|
||||||
|
// Use secp256k1 parameters manually since Go stdlib doesn't include it
|
||||||
|
curve = secp256k1Curve()
|
||||||
|
|
||||||
|
switch len(pubKey) {
|
||||||
|
case 65: // uncompressed: 0x04 || x || y
|
||||||
|
if pubKey[0] != 0x04 {
|
||||||
|
return nil, fmt.Errorf("invalid uncompressed pubkey prefix: %x", pubKey[0])
|
||||||
|
}
|
||||||
|
x := new(big.Int).SetBytes(pubKey[1:33])
|
||||||
|
y := new(big.Int).SetBytes(pubKey[33:65])
|
||||||
|
return &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil
|
||||||
|
|
||||||
|
case 33: // compressed: 0x02/0x03 || x
|
||||||
|
x, y := decompressPoint(curve, pubKey)
|
||||||
|
if x == nil {
|
||||||
|
return nil, fmt.Errorf("failed to decompress pubkey")
|
||||||
|
}
|
||||||
|
return &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid pubkey length: %d", len(pubKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func secp256k1Curve() elliptic.Curve {
|
||||||
|
return &secp256k1Params
|
||||||
|
}
|
||||||
|
|
||||||
|
var secp256k1Params = elliptic.CurveParams{
|
||||||
|
Name: "secp256k1",
|
||||||
|
BitSize: 256,
|
||||||
|
P: fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"),
|
||||||
|
N: fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
|
||||||
|
B: big.NewInt(7),
|
||||||
|
Gx: fromHex("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"),
|
||||||
|
Gy: fromHex("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromHex(s string) *big.Int {
|
||||||
|
i, _ := new(big.Int).SetString(s, 16)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func decompressPoint(curve elliptic.Curve, compressed []byte) (*big.Int, *big.Int) {
|
||||||
|
if len(compressed) != 33 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := compressed[0]
|
||||||
|
if prefix != 0x02 && prefix != 0x03 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
x := new(big.Int).SetBytes(compressed[1:])
|
||||||
|
p := curve.Params().P
|
||||||
|
|
||||||
|
// y² = x³ + 7 (for secp256k1)
|
||||||
|
x3 := new(big.Int).Mul(x, x)
|
||||||
|
x3.Mul(x3, x)
|
||||||
|
x3.Add(x3, big.NewInt(7))
|
||||||
|
x3.Mod(x3, p)
|
||||||
|
|
||||||
|
y := new(big.Int).ModSqrt(x3, p)
|
||||||
|
if y == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check parity
|
||||||
|
if (y.Bit(0) == 1) != (prefix == 0x03) {
|
||||||
|
y.Sub(p, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
return x, y
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
|
|
||||||
"code.sonr.org/go/did-it"
|
"code.sonr.org/go/did-it"
|
||||||
"code.sonr.org/go/did-it/crypto"
|
"code.sonr.org/go/did-it/crypto"
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
"code.sonr.org/go/ucan/pkg/command"
|
"code.sonr.org/go/ucan/pkg/command"
|
||||||
"code.sonr.org/go/ucan/pkg/policy"
|
"code.sonr.org/go/ucan/pkg/policy"
|
||||||
"code.sonr.org/go/ucan/token/delegation"
|
"code.sonr.org/go/ucan/token/delegation"
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelegationBuilder provides a fluent API for creating UCAN delegations.
|
// DelegationBuilder provides a fluent API for creating UCAN delegations.
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
"code.sonr.org/go/did-it"
|
"code.sonr.org/go/did-it"
|
||||||
"code.sonr.org/go/did-it/crypto"
|
"code.sonr.org/go/did-it/crypto"
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
"code.sonr.org/go/ucan/pkg/command"
|
"code.sonr.org/go/ucan/pkg/command"
|
||||||
"code.sonr.org/go/ucan/token/invocation"
|
"code.sonr.org/go/ucan/token/invocation"
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InvocationBuilder provides a fluent API for creating UCAN invocations.
|
// InvocationBuilder provides a fluent API for creating UCAN invocations.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package ucan
|
package ucan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
"code.sonr.org/go/ucan/pkg/policy"
|
"code.sonr.org/go/ucan/pkg/policy"
|
||||||
"code.sonr.org/go/ucan/pkg/policy/literal"
|
"code.sonr.org/go/ucan/pkg/policy/literal"
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolicyBuilder provides a fluent API for constructing UCAN policies.
|
// PolicyBuilder provides a fluent API for constructing UCAN policies.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package ucan
|
package ucan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
"code.sonr.org/go/ucan/pkg/policy"
|
"code.sonr.org/go/ucan/pkg/policy"
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidationErrorCode represents UCAN validation error types.
|
// ValidationErrorCode represents UCAN validation error types.
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package keybase
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"enclave/internal/crypto/mpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnclaveResult struct {
|
type EnclaveResult struct {
|
||||||
@@ -126,6 +128,29 @@ func (am *ActionManager) DeleteEnclave(ctx context.Context, enclaveID string) er
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *ActionManager) SignWithEnclave(ctx context.Context, enclaveID string, data []byte) ([]byte, error) {
|
||||||
|
am.kb.mu.RLock()
|
||||||
|
defer am.kb.mu.RUnlock()
|
||||||
|
|
||||||
|
enc, err := am.kb.queries.GetEnclaveByID(ctx, enclaveID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get enclave: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleEnc, err := mpc.ImportSimpleEnclave(
|
||||||
|
enc.PublicKey,
|
||||||
|
enc.ValShare,
|
||||||
|
enc.UserShare,
|
||||||
|
enc.Nonce,
|
||||||
|
mpc.CurveName(enc.Curve),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("import enclave: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return simpleEnc.Sign(data)
|
||||||
|
}
|
||||||
|
|
||||||
func enclaveToResult(enc *MpcEnclafe) *EnclaveResult {
|
func enclaveToResult(enc *MpcEnclafe) *EnclaveResult {
|
||||||
rotatedAt := ""
|
rotatedAt := ""
|
||||||
if enc.RotatedAt != nil {
|
if enc.RotatedAt != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandlerFunc func(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error)
|
type HandlerFunc func(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error)
|
||||||
@@ -15,6 +16,7 @@ var handlers = map[string]resourceHandlers{
|
|||||||
"accounts": {
|
"accounts": {
|
||||||
"list": handleAccountList,
|
"list": handleAccountList,
|
||||||
"get": handleAccountGet,
|
"get": handleAccountGet,
|
||||||
|
"sign": handleAccountSign,
|
||||||
},
|
},
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"list": handleCredentialList,
|
"list": handleCredentialList,
|
||||||
@@ -31,6 +33,7 @@ var handlers = map[string]resourceHandlers{
|
|||||||
"enclaves": {
|
"enclaves": {
|
||||||
"list": handleEnclaveList,
|
"list": handleEnclaveList,
|
||||||
"get": handleEnclaveGet,
|
"get": handleEnclaveGet,
|
||||||
|
"sign": handleEnclaveSign,
|
||||||
"rotate": handleEnclaveRotate,
|
"rotate": handleEnclaveRotate,
|
||||||
"archive": handleEnclaveArchive,
|
"archive": handleEnclaveArchive,
|
||||||
"delete": handleEnclaveDelete,
|
"delete": handleEnclaveDelete,
|
||||||
@@ -101,6 +104,32 @@ func handleAccountGet(ctx context.Context, am *ActionManager, subject string) (j
|
|||||||
return json.Marshal(account)
|
return json.Marshal(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAccountSign(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
|
||||||
|
if subject == "" {
|
||||||
|
return nil, errors.New("subject (hex-encoded data) required for sign action")
|
||||||
|
}
|
||||||
|
|
||||||
|
enclaves, err := am.ListEnclaves(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list enclaves: %w", err)
|
||||||
|
}
|
||||||
|
if len(enclaves) == 0 {
|
||||||
|
return nil, errors.New("no enclave available for signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := enclaves[0]
|
||||||
|
signature, err := am.SignWithEnclave(ctx, enc.EnclaveID, []byte(subject))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("sign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(map[string]string{
|
||||||
|
"signature": fmt.Sprintf("%x", signature),
|
||||||
|
"enclave_id": enc.EnclaveID,
|
||||||
|
"public_key": enc.PublicKeyHex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func handleCredentialList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
|
func handleCredentialList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
|
||||||
credentials, err := am.ListCredentials(ctx)
|
credentials, err := am.ListCredentials(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -179,6 +208,37 @@ func handleEnclaveGet(ctx context.Context, am *ActionManager, subject string) (j
|
|||||||
return json.Marshal(enc)
|
return json.Marshal(enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleEnclaveSign(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
|
||||||
|
if subject == "" {
|
||||||
|
return nil, errors.New("subject (enclave_id:hex_data) required for sign action")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(subject, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, errors.New("subject must be enclave_id:hex_data format")
|
||||||
|
}
|
||||||
|
|
||||||
|
enclaveID := parts[0]
|
||||||
|
data := []byte(parts[1])
|
||||||
|
|
||||||
|
signature, err := am.SignWithEnclave(ctx, enclaveID, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, _ := am.GetEnclaveByID(ctx, enclaveID)
|
||||||
|
pubKeyHex := ""
|
||||||
|
if enc != nil {
|
||||||
|
pubKeyHex = enc.PublicKeyHex
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(map[string]string{
|
||||||
|
"signature": fmt.Sprintf("%x", signature),
|
||||||
|
"enclave_id": enclaveID,
|
||||||
|
"public_key": pubKeyHex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func handleEnclaveRotate(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
|
func handleEnclaveRotate(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
|
||||||
if subject == "" {
|
if subject == "" {
|
||||||
return nil, errors.New("subject (enclave_id) required for rotate action")
|
return nil, errors.New("subject (enclave_id) required for rotate action")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"enclave/internal/crypto/bip44"
|
||||||
"enclave/internal/crypto/mpc"
|
"enclave/internal/crypto/mpc"
|
||||||
|
|
||||||
"github.com/ncruces/go-sqlite3"
|
"github.com/ncruces/go-sqlite3"
|
||||||
@@ -24,6 +25,12 @@ func RegisterMPCFunctions(conn *sqlite3.Conn) error {
|
|||||||
if err := registerRefreshFunction(conn); err != nil {
|
if err := registerRefreshFunction(conn); err != nil {
|
||||||
return fmt.Errorf("register mpc_refresh: %w", err)
|
return fmt.Errorf("register mpc_refresh: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := registerBIP44DeriveFunction(conn); err != nil {
|
||||||
|
return fmt.Errorf("register bip44_derive: %w", err)
|
||||||
|
}
|
||||||
|
if err := registerBIP44DeriveFromEnclaveFunction(conn); err != nil {
|
||||||
|
return fmt.Errorf("register bip44_derive_from_enclave: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,3 +228,84 @@ func updateSimpleEnclaveInDB(enclaveID string, enclave *mpc.SimpleEnclave) error
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bip44_derive(pubkey_hex, chain) -> address
|
||||||
|
// Derives a blockchain address from a public key for the specified chain.
|
||||||
|
// Supported chains: bitcoin, ethereum, cosmos, sonr
|
||||||
|
func registerBIP44DeriveFunction(conn *sqlite3.Conn) error {
|
||||||
|
return conn.CreateFunction("bip44_derive", 2, sqlite3.DETERMINISTIC|sqlite3.INNOCUOUS, func(ctx sqlite3.Context, args ...sqlite3.Value) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive requires 2 arguments: pubkey_hex, chain"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyHex := args[0].Text()
|
||||||
|
chain := args[1].Text()
|
||||||
|
|
||||||
|
if pubKeyHex == "" {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive: pubkey_hex cannot be empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if chain == "" {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive: chain cannot be empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyBytes, err := hex.DecodeString(pubKeyHex)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive: invalid pubkey hex: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := bip44.DeriveAddress(pubKeyBytes, chain)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.ResultText(address)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// bip44_derive_from_enclave(enclave_id, chain) -> address
|
||||||
|
// Derives a blockchain address from an MPC enclave's public key.
|
||||||
|
func registerBIP44DeriveFromEnclaveFunction(conn *sqlite3.Conn) error {
|
||||||
|
return conn.CreateFunction("bip44_derive_from_enclave", 2, sqlite3.DETERMINISTIC, func(ctx sqlite3.Context, args ...sqlite3.Value) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive_from_enclave requires 2 arguments: enclave_id, chain"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enclaveID := args[0].Text()
|
||||||
|
chain := args[1].Text()
|
||||||
|
|
||||||
|
if enclaveID == "" {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive_from_enclave: enclave_id cannot be empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if chain == "" {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive_from_enclave: chain cannot be empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kb := Get()
|
||||||
|
if kb == nil {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive_from_enclave: keybase not initialized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dbEnc, err := kb.queries.GetEnclaveByID(context.Background(), enclaveID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive_from_enclave: enclave not found: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := bip44.DeriveAddress(dbEnc.PublicKey, chain)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(fmt.Errorf("bip44_derive_from_enclave: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.ResultText(address)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user