mirror of
https://github.com/muun/recovery.git
synced 2025-02-23 03:22:31 -05:00
187 lines
4.9 KiB
Go
187 lines
4.9 KiB
Go
package libwallet
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"github.com/muun/libwallet/addresses"
|
|
"github.com/muun/libwallet/btcsuitew/txscriptw"
|
|
"github.com/muun/libwallet/musig"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
// CreateAddressV5 returns a P2TR MuunAddress using Musig with the signing and cosigning keys.
|
|
func CreateAddressV5(userKey, muunKey *HDPublicKey) (MuunAddress, error) {
|
|
return addresses.CreateAddressV5(&userKey.key, &muunKey.key, userKey.Path, userKey.Network.network)
|
|
}
|
|
|
|
type coinV5 struct {
|
|
Network *chaincfg.Params
|
|
OutPoint wire.OutPoint
|
|
KeyPath string
|
|
Amount btcutil.Amount
|
|
UserSessionId [32]byte
|
|
MuunPubNonce [66]byte
|
|
MuunPartialSig [32]byte
|
|
SigHashes *txscriptw.TaprootSigHashes
|
|
}
|
|
|
|
func (c *coinV5) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muunKey *HDPublicKey) error {
|
|
derivedUserKey, err := userKey.DeriveTo(c.KeyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to derive user private key: %w", err)
|
|
}
|
|
|
|
derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to derive muun public key: %w", err)
|
|
}
|
|
|
|
userEcPriv, err := derivedUserKey.key.ECPrivKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain ECPrivKey from derivedUserKey") // TODO: necessary handling?
|
|
}
|
|
|
|
muunEcPub, err := derivedMuunKey.key.ECPubKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain ECPubKey from derivedMuunKey") // TODO: necessary handling?
|
|
}
|
|
|
|
sigHash, err := txscriptw.CalcTaprootSigHash(tx, c.SigHashes, index, txscript.SigHashAll)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create sigHash: %w", err)
|
|
}
|
|
var toSign [32]byte
|
|
copy(toSign[:], sigHash)
|
|
|
|
return c.signSecondWith(index, tx, userEcPriv, muunEcPub, c.UserSessionId, toSign)
|
|
}
|
|
|
|
func (c *coinV5) FullySignInput(index int, tx *wire.MsgTx, userKey, muunKey *HDPrivateKey) error {
|
|
derivedUserKey, err := userKey.DeriveTo(c.KeyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to derive user private key: %w", err)
|
|
}
|
|
|
|
derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to derive muun private key: %w", err)
|
|
}
|
|
|
|
userEcPriv, err := derivedUserKey.key.ECPrivKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain ECPrivKey from derivedUserKey") // TODO: necessary handling?
|
|
}
|
|
|
|
muunEcPriv, err := derivedMuunKey.key.ECPrivKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain ECPrivKey from derivedMuunKey") // TODO: necessary handling?
|
|
}
|
|
|
|
sigHash, err := txscriptw.CalcTaprootSigHash(tx, c.SigHashes, index, txscript.SigHashAll)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create sigHash: %w", err)
|
|
}
|
|
var toSign [32]byte
|
|
copy(toSign[:], sigHash)
|
|
|
|
userPubNonce := musig.GeneratePubNonce(c.UserSessionId)
|
|
|
|
err = c.signFirstWith(index, tx, userEcPriv.PubKey(), muunEcPriv, userPubNonce, toSign)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.signSecondWith(index, tx, userEcPriv, muunEcPriv.PubKey(), c.UserSessionId, toSign)
|
|
}
|
|
|
|
func (c *coinV5) signFirstWith(
|
|
index int,
|
|
tx *wire.MsgTx,
|
|
userPub *btcec.PublicKey,
|
|
muunPriv *btcec.PrivateKey,
|
|
userPubNonce [66]byte,
|
|
toSign [32]byte,
|
|
) error {
|
|
|
|
// NOTE:
|
|
// This will only be called in a recovery context, where both private keys are provided by the
|
|
// user. We call the variables below "muunSessionId" and "muunPubNonce" to follow convention,
|
|
// but Muun servers play no role in this code path and both are locally generated.
|
|
muunSessionId := musig.RandomSessionId()
|
|
muunPubNonce := musig.GeneratePubNonce(muunSessionId)
|
|
|
|
muunPartialSig, err := musig.ComputeMuunPartialSignature(
|
|
toSign,
|
|
userPub,
|
|
muunPriv,
|
|
userPubNonce,
|
|
muunSessionId,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to add first signature: %w", err)
|
|
}
|
|
|
|
c.MuunPubNonce = muunPubNonce
|
|
c.MuunPartialSig = muunPartialSig
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *coinV5) signSecondWith(
|
|
index int,
|
|
tx *wire.MsgTx,
|
|
userPriv *btcec.PrivateKey,
|
|
muunPub *btcec.PublicKey,
|
|
userSessionId [32]byte,
|
|
toSign [32]byte,
|
|
) error {
|
|
|
|
rawCombinedSig, err := musig.AddUserSignatureAndCombine(
|
|
toSign,
|
|
userPriv,
|
|
muunPub,
|
|
c.MuunPartialSig,
|
|
c.MuunPubNonce,
|
|
userSessionId,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to add second signature and combine: %w", err)
|
|
}
|
|
|
|
sig := append(rawCombinedSig[:], byte(txscript.SigHashAll))
|
|
|
|
tx.TxIn[index].Witness = wire.TxWitness{sig}
|
|
return nil
|
|
}
|
|
|
|
type MusigNonces struct {
|
|
sessionIds [][32]byte
|
|
publicNonces [][66]byte
|
|
}
|
|
|
|
func (m *MusigNonces) GetPubnonceHex(index int) string {
|
|
return hex.EncodeToString(m.publicNonces[index][:])
|
|
}
|
|
|
|
func GenerateMusigNonces(count int) *MusigNonces {
|
|
sessionIds := make([][32]byte, 0)
|
|
publicNonces := make([][66]byte, 0)
|
|
|
|
for i := 0; i < count; i += 1 {
|
|
sessionIds = append(sessionIds, musig.RandomSessionId())
|
|
publicNonces = append(publicNonces, musig.GeneratePubNonce(sessionIds[i]))
|
|
}
|
|
|
|
return &MusigNonces{
|
|
sessionIds,
|
|
publicNonces,
|
|
}
|
|
} |