Update project structure and build process

This commit is contained in:
Juan Pablo Civile
2025-05-13 11:10:08 -03:00
parent 124e9fa1bc
commit d9f3e925a4
277 changed files with 15321 additions and 930 deletions

View File

@@ -0,0 +1,62 @@
package addresses
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
)
const (
V1 = 1
V2 = 2
V3 = 3
V4 = 4
V5 = 5
SubmarineSwapV1 = 101
SubmarineSwapV2 = 102
IncomingSwap = 201
)
type WalletAddress struct {
version int
derivationPath string
address string
}
func New(version int, derivationPath string, address string) *WalletAddress {
return &WalletAddress{
version: version,
derivationPath: derivationPath,
address: address,
}
}
func Create(version int, userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
switch version {
case V1:
return CreateAddressV1(userKey, path, network)
case V2:
return CreateAddressV2(userKey, muunKey, path, network)
case V3:
return CreateAddressV3(userKey, muunKey, path, network)
case V4:
return CreateAddressV4(userKey, muunKey, path, network)
case V5:
return CreateAddressV5(userKey, muunKey, path, network)
default:
return nil, fmt.Errorf("unknown or unsupported version %v", version)
}
}
func (a *WalletAddress) Version() int {
return a.version
}
func (a *WalletAddress) DerivationPath() string {
return a.derivationPath
}
func (a *WalletAddress) Address() string {
return a.address
}

View File

@@ -0,0 +1,30 @@
package addresses
import (
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/muun/libwallet/hdpath"
)
func parseKey(s string) *hdkeychain.ExtendedKey {
key, err := hdkeychain.NewKeyFromString(s)
if err != nil {
panic(err)
}
return key
}
func derive(key *hdkeychain.ExtendedKey, fromPath, toPath string) *hdkeychain.ExtendedKey {
indexes := hdpath.MustParse(toPath).IndexesFrom(hdpath.MustParse(fromPath))
for _, index := range indexes {
var err error
var modifier uint32
if index.Hardened {
modifier = hdkeychain.HardenedKeyStart
}
key, err = key.Child(index.Index | modifier)
if err != nil {
panic(err)
}
}
return key
}

24
libwallet/addresses/v1.go Normal file
View File

@@ -0,0 +1,24 @@
package addresses
import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
)
// CreateAddressV1 returns a P2PKH WalletAddress from a publicKey for use in TransactionSchemeV1
func CreateAddressV1(userKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
pubKey, err := userKey.ECPubKey()
if err != nil {
return nil, err
}
address, err := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), network)
if err != nil {
return nil, err
}
return &WalletAddress{
address: address.EncodeAddress(),
version: V1,
derivationPath: path,
}, nil
}

59
libwallet/addresses/v2.go Normal file
View File

@@ -0,0 +1,59 @@
package addresses
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/pkg/errors"
)
func CreateAddressV2(userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
script, err := CreateRedeemScriptV2(userKey, muunKey, network)
if err != nil {
return nil, fmt.Errorf("failed to generate redeem script v2: %w", err)
}
address, err := btcutil.NewAddressScriptHash(script, network)
if err != nil {
return nil, fmt.Errorf("failed to generate multisig address: %w", err)
}
return &WalletAddress{
address: address.EncodeAddress(),
version: V2,
derivationPath: path,
}, nil
}
func CreateRedeemScriptV2(userKey, muunKey *hdkeychain.ExtendedKey, network *chaincfg.Params) ([]byte, error) {
return createMultisigRedeemScript(userKey, muunKey, network)
}
func createMultisigRedeemScript(userKey, muunKey *hdkeychain.ExtendedKey, network *chaincfg.Params) ([]byte, error) {
userPublicKey, err := userKey.ECPubKey()
if err != nil {
return nil, err
}
userAddress, err := btcutil.NewAddressPubKey(userPublicKey.SerializeCompressed(), network)
if err != nil {
return nil, errors.Wrapf(err, "failed to generate address for user")
}
muunPublicKey, err := muunKey.ECPubKey()
if err != nil {
return nil, err
}
WalletAddress, err := btcutil.NewAddressPubKey(muunPublicKey.SerializeCompressed(), network)
if err != nil {
return nil, errors.Wrapf(err, "failed to generate address for muun")
}
return txscript.MultiSigScript([]*btcutil.AddressPubKey{
userAddress,
WalletAddress,
}, 2)
}

57
libwallet/addresses/v2_test.go Executable file
View File

@@ -0,0 +1,57 @@
package addresses
import (
"reflect"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
)
var network = &chaincfg.RegressionNetParams
func TestCreateAddressV2(t *testing.T) {
const (
addressPath = "m/schema:1'/recovery:1'/external:1/0"
originAddress = "2NDeWrsJEwvxwVnvtWzPjhDC5B2LYkFuX2s"
encodedMuunKey = "tpubDBYMnFoxYLdMBZThTk4uARTe4kGPeEYWdKcaEzaUxt1cesetnxtTqmAxVkzDRou51emWytommyLWcF91SdF5KecA6Ja8oHK1FF7d5U2hMxX"
encodedUserKey = "tprv8dfM4H5fYJirMai5Er3LguicgUAyxmcSQbFub5ens16amX1e1HAFiW4SXnFVw9nu9FedFQqTPGTTjPEmgfvvXMKww3UcRpFbbC4DFjbCcTb"
basePath = "m/schema:1'/recovery:1'"
v2EncodedScript = "5221029fa5af7a34c142c1ce348b360abeb7de01df25b1d50129e58a67a6b846c9303b21025714f6b3670d4a38f5e2d6e8f239c9fc072543ce33dca54fcb4f4886a5cb87a652ae"
)
baseMuunKey := parseKey(encodedMuunKey)
muunKey := derive(baseMuunKey, basePath, addressPath)
baseUserKey := parseKey(encodedUserKey)
userKey := derive(baseUserKey, basePath, addressPath)
type args struct {
userKey *hdkeychain.ExtendedKey
muunKey *hdkeychain.ExtendedKey
}
tests := []struct {
name string
args args
want *WalletAddress
wantErr bool
}{
{name: "gen address",
args: args{userKey: userKey, muunKey: muunKey},
want: &WalletAddress{address: originAddress, derivationPath: addressPath, version: V2}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateAddressV2(tt.args.userKey, tt.args.muunKey, addressPath, network)
if (err != nil) != tt.wantErr {
t.Errorf("CreateAddressV2() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateAddressV2() = %v, want %v", got, tt.want)
}
})
}
}

55
libwallet/addresses/v3.go Normal file
View File

@@ -0,0 +1,55 @@
package addresses
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
)
func CreateAddressV3(userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
redeemScript, err := CreateRedeemScriptV3(userKey, muunKey, network)
if err != nil {
return nil, err
}
address, err := btcutil.NewAddressScriptHash(redeemScript, network)
if err != nil {
return nil, err
}
return &WalletAddress{
address: address.EncodeAddress(),
version: V3,
derivationPath: path,
}, nil
}
func CreateRedeemScriptV3(userKey, muunKey *hdkeychain.ExtendedKey, network *chaincfg.Params) ([]byte, error) {
witnessScript, err := CreateWitnessScriptV3(userKey, muunKey, network)
if err != nil {
return nil, fmt.Errorf("failed to generate redeem script v3: %w", err)
}
return createNonNativeSegwitRedeemScript(witnessScript)
}
func CreateWitnessScriptV3(userKey, muunKey *hdkeychain.ExtendedKey, network *chaincfg.Params) ([]byte, error) {
// createMultisigRedeemScript creates a valid script for both V2 and V3 schemes
return createMultisigRedeemScript(userKey, muunKey, network)
}
func createNonNativeSegwitRedeemScript(witnessScript []byte) ([]byte, error) {
witnessScriptHash := sha256.Sum256(witnessScript)
builder := txscript.NewScriptBuilder()
builder.AddInt64(0)
builder.AddData(witnessScriptHash[:])
return builder.Script()
}

54
libwallet/addresses/v3_test.go Executable file
View File

@@ -0,0 +1,54 @@
package addresses
import (
"reflect"
"testing"
"github.com/btcsuite/btcutil/hdkeychain"
)
func TestCreateAddressV3(t *testing.T) {
const (
addressPath = "m/schema:1'/recovery:1'/external:1/0"
v3Address = "2MswEXmCLaHQq6pUTtnUVF8wVArfYSqUec5"
basePK = "tpubDAN21T1DFREQQS4FvpUktKRBzXXsj5ddenAa5u198hLXvErFFR4Lj8bt8xMG3xnZr6u8mx1vrFW9RwCDXQwQuYRCLq1j9Nr2VJUrENzteQH"
baseCosigningPK = "tpubDAsVhzq6otpasovieofhiaY38bSFGyJaBGvrJjBv9whhSnftUXfMTMVrq4BbTXT5A9b78CqqbPuM2j1ZGWdiggd7JHUTZAHh8GXDTt4Pkj9"
basePath = "m/schema:1'/recovery:1'"
v3EncodedScript = "0020e1fbfbd395aff8b4087fee3e4488815ef659b559b3cd0d6800b5a591efd99f38"
)
baseMuunKey := parseKey(baseCosigningPK)
muunKey := derive(baseMuunKey, basePath, addressPath)
baseUserKey := parseKey(basePK)
userKey := derive(baseUserKey, basePath, addressPath)
type args struct {
userKey *hdkeychain.ExtendedKey
muunKey *hdkeychain.ExtendedKey
}
tests := []struct {
name string
args args
want *WalletAddress
wantErr bool
}{
{name: "gen address",
args: args{userKey: userKey, muunKey: muunKey},
want: &WalletAddress{address: v3Address, derivationPath: addressPath, version: V3}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateAddressV3(tt.args.userKey, tt.args.muunKey, addressPath, network)
if (err != nil) != tt.wantErr {
t.Errorf("CreateAddressV3() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateAddressV3() = %v, want %v", got, tt.want)
}
})
}
}

36
libwallet/addresses/v4.go Normal file
View File

@@ -0,0 +1,36 @@
package addresses
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
)
// CreateAddressV4 returns a P2WSH WalletAddress from a user HD-pubkey and a Muun co-signing HD-pubkey.
func CreateAddressV4(userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
witnessScript, err := CreateWitnessScriptV4(userKey, muunKey, network)
if err != nil {
return nil, fmt.Errorf("failed to generate witness script v4: %w", err)
}
witnessScript256 := sha256.Sum256(witnessScript)
address, err := btcutil.NewAddressWitnessScriptHash(witnessScript256[:], network)
if err != nil {
return nil, err
}
return &WalletAddress{
address: address.EncodeAddress(),
version: V4,
derivationPath: path,
}, nil
}
func CreateWitnessScriptV4(userKey, muunKey *hdkeychain.ExtendedKey, network *chaincfg.Params) ([]byte, error) {
// createMultisigRedeemScript creates a valid script for V2, V3 and V4 schemes
return createMultisigRedeemScript(userKey, muunKey, network)
}

52
libwallet/addresses/v4_test.go Executable file
View File

@@ -0,0 +1,52 @@
package addresses
import (
"reflect"
"testing"
"github.com/btcsuite/btcutil/hdkeychain"
)
func TestCreateAddressV4(t *testing.T) {
const (
addressPath = "m/schema:1'/recovery:1'/external:1/2"
v4Address = "bcrt1qrs3vk4dzv70syck2qdz3g06tgckq4pftenuk5p77st9glnskpvtqe2tvvk"
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)
type args struct {
userKey *hdkeychain.ExtendedKey
muunKey *hdkeychain.ExtendedKey
}
tests := []struct {
name string
args args
want *WalletAddress
wantErr bool
}{
{name: "gen bech32 address",
args: args{userKey: userKey, muunKey: muunKey},
want: &WalletAddress{address: v4Address, derivationPath: addressPath, version: V4}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateAddressV4(tt.args.userKey, tt.args.muunKey, addressPath, network)
if (err != nil) != tt.wantErr {
t.Errorf("CreateAddressV4() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateAddressV4() = %v, want %v", got, tt.want)
}
})
}
}

50
libwallet/addresses/v5.go Normal file
View File

@@ -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
}

View File

@@ -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)
}
}