312 lines
8.2 KiB
Go
312 lines
8.2 KiB
Go
package keybase
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"enclave/internal/crypto/bip44"
|
|
"enclave/internal/crypto/mpc"
|
|
|
|
"github.com/ncruces/go-sqlite3"
|
|
)
|
|
|
|
func RegisterMPCFunctions(conn *sqlite3.Conn) error {
|
|
if err := registerGenerateFunction(conn); err != nil {
|
|
return fmt.Errorf("register mpc_generate: %w", err)
|
|
}
|
|
if err := registerSignFunction(conn); err != nil {
|
|
return fmt.Errorf("register mpc_sign: %w", err)
|
|
}
|
|
if err := registerVerifyFunction(conn); err != nil {
|
|
return fmt.Errorf("register mpc_verify: %w", err)
|
|
}
|
|
if err := registerRefreshFunction(conn); err != nil {
|
|
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
|
|
}
|
|
|
|
func registerGenerateFunction(conn *sqlite3.Conn) error {
|
|
return conn.CreateFunction("mpc_generate", 0, 0, func(ctx sqlite3.Context, args ...sqlite3.Value) {
|
|
enc, err := mpc.NewSimpleEnclave()
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_generate: %w", err))
|
|
return
|
|
}
|
|
|
|
idBytes := make([]byte, 8)
|
|
rand.Read(idBytes)
|
|
enclaveID := fmt.Sprintf("enc_%x", idBytes)
|
|
|
|
kb := Get()
|
|
if kb == nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_generate: keybase not initialized"))
|
|
return
|
|
}
|
|
|
|
am, err := NewActionManager()
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_generate: action manager: %w", err))
|
|
return
|
|
}
|
|
|
|
_, err = am.CreateEnclave(context.Background(), NewEnclaveInput{
|
|
EnclaveID: enclaveID,
|
|
PublicKeyHex: enc.PubKeyHex(),
|
|
PublicKey: enc.PubKeyBytes(),
|
|
ValShare: enc.GetShare1(),
|
|
UserShare: enc.GetShare2(),
|
|
Nonce: enc.GetNonce(),
|
|
Curve: string(enc.GetCurve()),
|
|
})
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_generate: create enclave: %w", err))
|
|
return
|
|
}
|
|
|
|
ctx.ResultText(enclaveID)
|
|
})
|
|
}
|
|
|
|
func registerSignFunction(conn *sqlite3.Conn) error {
|
|
return conn.CreateFunction("mpc_sign", 2, sqlite3.DETERMINISTIC, func(ctx sqlite3.Context, args ...sqlite3.Value) {
|
|
if len(args) != 2 {
|
|
ctx.ResultError(fmt.Errorf("mpc_sign requires 2 arguments: enclave_id, data"))
|
|
return
|
|
}
|
|
|
|
enclaveID := args[0].Text()
|
|
data := args[1].RawBlob()
|
|
|
|
if enclaveID == "" {
|
|
ctx.ResultError(fmt.Errorf("mpc_sign: enclave_id cannot be empty"))
|
|
return
|
|
}
|
|
if len(data) == 0 {
|
|
ctx.ResultError(fmt.Errorf("mpc_sign: data cannot be empty"))
|
|
return
|
|
}
|
|
|
|
enclave, err := loadSimpleEnclaveFromDB(enclaveID)
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_sign: %w", err))
|
|
return
|
|
}
|
|
|
|
signature, err := enclave.Sign(data)
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_sign: signing failed: %w", err))
|
|
return
|
|
}
|
|
|
|
ctx.ResultBlob(signature)
|
|
})
|
|
}
|
|
|
|
func registerVerifyFunction(conn *sqlite3.Conn) error {
|
|
return conn.CreateFunction("mpc_verify", 3, sqlite3.DETERMINISTIC|sqlite3.INNOCUOUS, func(ctx sqlite3.Context, args ...sqlite3.Value) {
|
|
if len(args) != 3 {
|
|
ctx.ResultError(fmt.Errorf("mpc_verify requires 3 arguments: public_key_hex, data, signature"))
|
|
return
|
|
}
|
|
|
|
pubKeyHex := args[0].Text()
|
|
data := args[1].RawBlob()
|
|
signature := args[2].RawBlob()
|
|
|
|
if pubKeyHex == "" {
|
|
ctx.ResultError(fmt.Errorf("mpc_verify: public_key_hex cannot be empty"))
|
|
return
|
|
}
|
|
if len(data) == 0 {
|
|
ctx.ResultError(fmt.Errorf("mpc_verify: data cannot be empty"))
|
|
return
|
|
}
|
|
if len(signature) == 0 {
|
|
ctx.ResultError(fmt.Errorf("mpc_verify: signature cannot be empty"))
|
|
return
|
|
}
|
|
|
|
pubKeyBytes, err := hex.DecodeString(pubKeyHex)
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_verify: invalid public key hex: %w", err))
|
|
return
|
|
}
|
|
|
|
valid, err := mpc.VerifyWithPubKey(pubKeyBytes, data, signature)
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_verify: verification error: %w", err))
|
|
return
|
|
}
|
|
|
|
if valid {
|
|
ctx.ResultInt(1)
|
|
} else {
|
|
ctx.ResultInt(0)
|
|
}
|
|
})
|
|
}
|
|
|
|
func registerRefreshFunction(conn *sqlite3.Conn) error {
|
|
return conn.CreateFunction("mpc_refresh", 1, 0, func(ctx sqlite3.Context, args ...sqlite3.Value) {
|
|
if len(args) != 1 {
|
|
ctx.ResultError(fmt.Errorf("mpc_refresh requires 1 argument: enclave_id"))
|
|
return
|
|
}
|
|
|
|
enclaveID := args[0].Text()
|
|
if enclaveID == "" {
|
|
ctx.ResultError(fmt.Errorf("mpc_refresh: enclave_id cannot be empty"))
|
|
return
|
|
}
|
|
|
|
enclave, err := loadSimpleEnclaveFromDB(enclaveID)
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_refresh: %w", err))
|
|
return
|
|
}
|
|
|
|
newEnclave, err := enclave.Refresh()
|
|
if err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_refresh: refresh failed: %w", err))
|
|
return
|
|
}
|
|
|
|
if err := updateSimpleEnclaveInDB(enclaveID, newEnclave); err != nil {
|
|
ctx.ResultError(fmt.Errorf("mpc_refresh: update failed: %w", err))
|
|
return
|
|
}
|
|
|
|
ctx.ResultText(enclaveID)
|
|
})
|
|
}
|
|
|
|
func loadSimpleEnclaveFromDB(enclaveID string) (*mpc.SimpleEnclave, error) {
|
|
kb := Get()
|
|
if kb == nil {
|
|
return nil, fmt.Errorf("keybase not initialized")
|
|
}
|
|
|
|
dbEnc, err := kb.queries.GetEnclaveByID(context.Background(), enclaveID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("enclave not found: %w", err)
|
|
}
|
|
|
|
return mpc.ImportSimpleEnclave(
|
|
dbEnc.PublicKey,
|
|
dbEnc.ValShare,
|
|
dbEnc.UserShare,
|
|
dbEnc.Nonce,
|
|
mpc.CurveName(dbEnc.Curve),
|
|
)
|
|
}
|
|
|
|
func updateSimpleEnclaveInDB(enclaveID string, enclave *mpc.SimpleEnclave) error {
|
|
kb := Get()
|
|
if kb == nil {
|
|
return fmt.Errorf("keybase not initialized")
|
|
}
|
|
|
|
dbEnc, err := kb.queries.GetEnclaveByID(context.Background(), enclaveID)
|
|
if err != nil {
|
|
return fmt.Errorf("get enclave: %w", err)
|
|
}
|
|
|
|
_, err = kb.db.ExecContext(context.Background(), `
|
|
UPDATE mpc_enclaves
|
|
SET val_share = ?, user_share = ?, nonce = ?, rotated_at = datetime('now')
|
|
WHERE id = ?
|
|
`, enclave.GetShare1(), enclave.GetShare2(), enclave.GetNonce(), dbEnc.ID)
|
|
|
|
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)
|
|
})
|
|
}
|