Skip to content

Commit

Permalink
Release v0.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
slezica committed Nov 11, 2021
1 parent d485ec7 commit 0d84d61
Show file tree
Hide file tree
Showing 290 changed files with 79,091 additions and 1,937 deletions.
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

3 changes: 2 additions & 1 deletion V1.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/muun/libwallet/addresses"
"github.com/muun/libwallet/btcsuitew/txscriptw"
)

// CreateAddressV1 returns a P2PKH MuunAddress from a publicKey for use in TransactionSchemeV1
Expand Down Expand Up @@ -56,7 +57,7 @@ func (c *coinV1) createRedeemScript(publicKey *HDPublicKey) ([]byte, error) {
return nil, fmt.Errorf("failed to generate address for user: %w", err)
}

return txscript.PayToAddrScript(userAddress.AddressPubKeyHash())
return txscriptw.PayToAddrScript(userAddress.AddressPubKeyHash())
}

func (c *coinV1) signature(index int, tx *wire.MsgTx, userKey *HDPrivateKey) ([]byte, error) {
Expand Down
187 changes: 187 additions & 0 deletions V5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
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,
}
}
23 changes: 14 additions & 9 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import (
"strings"

"github.com/muun/libwallet/addresses"
"github.com/muun/libwallet/btcsuitew/btcutilw"
"github.com/muun/libwallet/errors"

"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"google.golang.org/protobuf/proto"
)

// These constants are here for clients usage.
const (
AddressVersionV1 = addresses.V1
AddressVersionV2 = addresses.V2
AddressVersionV3 = addresses.V3
AddressVersionV4 = addresses.V4
AddressVersionV5 = addresses.V5
AddressVersionSwapsV1 = addresses.SubmarineSwapV1
AddressVersionSwapsV2 = addresses.SubmarineSwapV2
)
Expand Down Expand Up @@ -52,12 +57,12 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {
return nil, errors.New(ErrInvalidURI, "Invalid scheme")
}

base58Address := components.Opaque
address := components.Opaque

// When URIs are bitcoin:// the address comes in host
// this happens in iOS that mostly ignores bitcoin: format
if len(base58Address) == 0 {
base58Address = components.Host
if len(address) == 0 {
address = components.Host
}

queryValues, err := url.ParseQuery(components.RawQuery)
Expand Down Expand Up @@ -89,9 +94,9 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {

//BIP70 check
if len(queryValues["r"]) != 0 {
if len(base58Address) > 0 {
if len(address) > 0 {
return &MuunPaymentURI{
Address: base58Address,
Address: address,
Label: label,
Message: message,
Amount: amount,
Expand All @@ -109,17 +114,17 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {
}

// Bech32 check
validatedBase58Address, err := btcutil.DecodeAddress(base58Address, network.network)
decodedAddress, err := btcutilw.DecodeAddress(address, network.network)
if err != nil {
return nil, fmt.Errorf("invalid address: %w", err)
}

if !validatedBase58Address.IsForNet(network.network) {
if !decodedAddress.IsForNet(network.network) {
return nil, errors.New(ErrInvalidURI, "Network mismatch")
}

return &MuunPaymentURI{
Address: validatedBase58Address.String(),
Address: decodedAddress.String(),
Label: label,
Message: message,
Amount: amount,
Expand Down
1 change: 1 addition & 0 deletions addresses/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
V2 = 2
V3 = 3
V4 = 4
V5 = 5
SubmarineSwapV1 = 101
SubmarineSwapV2 = 102
IncomingSwap = 201
Expand Down
50 changes: 50 additions & 0 deletions addresses/v5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package addresses

import (
"fmt"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/muun/libwallet/btcsuitew/btcutilw"
"github.com/muun/libwallet/musig"
)

// CreateAddressV5 returns a P2TR WalletAddress using Musig with the signing and cosigning keys.
func CreateAddressV5(userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
witnessProgram, err := CreateWitnessScriptV5(userKey, muunKey)
if err != nil {
return nil, fmt.Errorf("failed to generate witness script v5: %w", err)
}

address, err := btcutilw.NewAddressTaprootKey(witnessProgram, network)
if err != nil {
return nil, err
}

return &WalletAddress{
address: address.EncodeAddress(),
version: V5,
derivationPath: path,
}, nil
}

func CreateWitnessScriptV5(userKey, muunKey *hdkeychain.ExtendedKey) ([]byte, error) {
userPublicKey, err := userKey.ECPubKey()
if err != nil {
return nil, err
}

muunPublicKey, err := muunKey.ECPubKey()
if err != nil {
return nil, err
}

combined, err := musig.CombinePubKeysWithTweak(userPublicKey, muunPublicKey, nil)
if err != nil {
return nil, err
}

xOnlyCombined := combined.SerializeCompressed()[1:]

return xOnlyCombined, nil
}
34 changes: 34 additions & 0 deletions addresses/v5_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package addresses

import (
"reflect"
"testing"
)

func TestCreateAddressV5(t *testing.T) {
const (
addressPath = "m/schema:1'/recovery:1'/external:1/17"

v5Address = "bcrt1pvqngr85tm8hmsv2hjyrejlpsy7u65f7vke8mmrxnyuj3aj3xsapqvh8yrf"
basePK = "tpubDBf5wCeqg3KrLJiXaveDzD5JtFJ1ss9NVvFMx4RYS73SjwPEEawcAQ7V1B5DGM4gunWDeYNrnkc49sUaf7mS1wUKiJJQD6WEctExUQoLvrg"
baseCosigningPK = "tpubDB22PFkUaHoB7sgxh7exCivV5rAevVSzbB8WkFCCdbHq39r8xnYexiot4NGbi8PM6E1ySVeaHsoDeMYb6EMndpFrzVmuX8iQNExzwNpU61B"
basePath = "m/schema:1'/recovery:1'"
)

baseMuunKey := parseKey(baseCosigningPK)
muunKey := derive(baseMuunKey, basePath, addressPath)

baseUserKey := parseKey(basePK)
userKey := derive(baseUserKey, basePath, addressPath)

expectedAddr := &WalletAddress{address: v5Address, derivationPath: addressPath, version: V5}

actualAddr, err := CreateAddressV5(userKey, muunKey, addressPath, network)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(actualAddr, expectedAddr) {
t.Errorf("Created v5 address %v, expected %v", actualAddr, expectedAddr)
}
}
Loading

0 comments on commit 0d84d61

Please sign in to comment.