From 1bcb802818f464754fa7b3642ad78bb9fa519874 Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 12:27:51 +0100 Subject: [PATCH 1/8] Implemented wallet version 2 with unique derivation path: Following the recent transition from Kaspa coin type (111111) to Karlsen coin type (121337) - a change already documented on https://github.com/satoshilabs/slips/blob/master/slip-0044.md - we're in the process of adopting our specific BIP39 derivation path. This step is crucial for most integrations that do not operate a node daemon, such as hardware wallets, to ensure address compatibility across different platforms. A unique derivation path is essential for this purpose. We currently have wallets that use the old coin type, necessitating support for both the legacy and new derivation paths. To facilitate this, a new command line option has been introduced for the 'create' subcommand: -l (short) or --legacy (long). Here's how it functions: * By default, the command creates or imports a version 2 wallet using the new coin type 121337. * When the legacy option is invoked, it creates or imports a version 1 wallet with the original coin type 111111. Although wallets with version 1 will remain operational, migrating KLS to a newly established version 2 wallet is strongly advised for future compatibility with both software and hardware wallets. Final implementation for commit e20a0901bd1826ded24b02c4f8d6fd23f7759f86 --- cmd/karlsenwallet/config.go | 1 + cmd/karlsenwallet/create.go | 18 +++++++++++++++--- cmd/karlsenwallet/daemon/server/sign.go | 2 +- cmd/karlsenwallet/dump_unencrypted_data.go | 2 +- cmd/karlsenwallet/keys/create.go | 12 ++++++------ cmd/karlsenwallet/keys/keys.go | 4 ++-- cmd/karlsenwallet/libkarlsenwallet/bip39.go | 16 ++++++++++++---- cmd/karlsenwallet/libkarlsenwallet/sign.go | 8 ++++---- cmd/karlsenwallet/send.go | 2 +- cmd/karlsenwallet/sign.go | 2 +- 10 files changed, 44 insertions(+), 23 deletions(-) diff --git a/cmd/karlsenwallet/config.go b/cmd/karlsenwallet/config.go index 1b17c002fc..c14b20def8 100644 --- a/cmd/karlsenwallet/config.go +++ b/cmd/karlsenwallet/config.go @@ -42,6 +42,7 @@ type createConfig struct { NumPublicKeys uint32 `long:"num-public-keys" short:"n" description:"Total number of keys" default:"1"` ECDSA bool `long:"ecdsa" description:"Create an ECDSA wallet"` Import bool `long:"import" short:"i" description:"Import private keys (as opposed to generating them)"` + Legacy bool `long:"legacy" short:"l" description:"Use legacy wallet and derivation path"` config.NetworkFlags } diff --git a/cmd/karlsenwallet/create.go b/cmd/karlsenwallet/create.go index a4031d44ff..ceffad11e4 100644 --- a/cmd/karlsenwallet/create.go +++ b/cmd/karlsenwallet/create.go @@ -17,11 +17,23 @@ func create(conf *createConfig) error { var encryptedMnemonics []*keys.EncryptedMnemonic var signerExtendedPublicKeys []string var err error + var version uint32 isMultisig := conf.NumPublicKeys > 1 + + if !conf.Legacy { + + // Version 2 uses new derivation path. + version = keys.LastVersion + } else { + + // Version 1 uses old derivation path. + version = 1 + } + if !conf.Import { - encryptedMnemonics, signerExtendedPublicKeys, err = keys.CreateMnemonics(conf.NetParams(), conf.NumPrivateKeys, conf.Password, isMultisig) + encryptedMnemonics, signerExtendedPublicKeys, err = keys.CreateMnemonics(conf.NetParams(), conf.NumPrivateKeys, conf.Password, isMultisig, version) } else { - encryptedMnemonics, signerExtendedPublicKeys, err = keys.ImportMnemonics(conf.NetParams(), conf.NumPrivateKeys, conf.Password, isMultisig) + encryptedMnemonics, signerExtendedPublicKeys, err = keys.ImportMnemonics(conf.NetParams(), conf.NumPrivateKeys, conf.Password, isMultisig, version) } if err != nil { return err @@ -65,7 +77,7 @@ func create(conf *createConfig) error { } file := keys.File{ - Version: keys.LastVersion, + Version: version, EncryptedMnemonics: encryptedMnemonics, ExtendedPublicKeys: extendedPublicKeys, MinimumSignatures: conf.MinimumSignatures, diff --git a/cmd/karlsenwallet/daemon/server/sign.go b/cmd/karlsenwallet/daemon/server/sign.go index 254e2710c1..e9b8cf3dfa 100644 --- a/cmd/karlsenwallet/daemon/server/sign.go +++ b/cmd/karlsenwallet/daemon/server/sign.go @@ -26,7 +26,7 @@ func (s *server) signTransactions(unsignedTransactions [][]byte, password string } signedTransactions := make([][]byte, len(unsignedTransactions)) for i, unsignedTransaction := range unsignedTransactions { - signedTransaction, err := libkarlsenwallet.Sign(s.params, mnemonics, unsignedTransaction, s.keysFile.ECDSA) + signedTransaction, err := libkarlsenwallet.Sign(s.params, mnemonics, unsignedTransaction, s.keysFile.ECDSA, s.keysFile.Version) if err != nil { return nil, err } diff --git a/cmd/karlsenwallet/dump_unencrypted_data.go b/cmd/karlsenwallet/dump_unencrypted_data.go index 0477a954cc..f9719eaaf5 100644 --- a/cmd/karlsenwallet/dump_unencrypted_data.go +++ b/cmd/karlsenwallet/dump_unencrypted_data.go @@ -36,7 +36,7 @@ func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error { mnemonicPublicKeys := make(map[string]struct{}) for i, mnemonic := range mnemonics { fmt.Printf("Mnemonic #%d:\n%s\n\n", i+1, mnemonic) - publicKey, err := libkarlsenwallet.MasterPublicKeyFromMnemonic(conf.NetParams(), mnemonic, len(keysFile.ExtendedPublicKeys) > 1) + publicKey, err := libkarlsenwallet.MasterPublicKeyFromMnemonic(conf.NetParams(), mnemonic, len(keysFile.ExtendedPublicKeys) > 1, keysFile.Version) if err != nil { return err } diff --git a/cmd/karlsenwallet/keys/create.go b/cmd/karlsenwallet/keys/create.go index eb083c841f..1d18d04233 100644 --- a/cmd/karlsenwallet/keys/create.go +++ b/cmd/karlsenwallet/keys/create.go @@ -15,7 +15,7 @@ import ( ) // CreateMnemonics generates `numKeys` number of mnemonics. -func CreateMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword string, isMultisig bool) (encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) { +func CreateMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword string, isMultisig bool, version uint32) (encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) { mnemonics := make([]string, numKeys) for i := uint32(0); i < numKeys; i++ { var err error @@ -25,11 +25,11 @@ func CreateMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword s } } - return encryptedMnemonicExtendedPublicKeyPairs(params, mnemonics, cmdLinePassword, isMultisig) + return encryptedMnemonicExtendedPublicKeyPairs(params, mnemonics, cmdLinePassword, isMultisig, version) } // ImportMnemonics imports a `numKeys` of mnemonics. -func ImportMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword string, isMultisig bool) (encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) { +func ImportMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword string, isMultisig bool, version uint32) (encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) { mnemonics := make([]string, numKeys) for i := uint32(0); i < numKeys; i++ { fmt.Printf("Enter mnemonic #%d here:\n", i+1) @@ -45,10 +45,10 @@ func ImportMnemonics(params *dagconfig.Params, numKeys uint32, cmdLinePassword s mnemonics[i] = string(mnemonic) } - return encryptedMnemonicExtendedPublicKeyPairs(params, mnemonics, cmdLinePassword, isMultisig) + return encryptedMnemonicExtendedPublicKeyPairs(params, mnemonics, cmdLinePassword, isMultisig, version) } -func encryptedMnemonicExtendedPublicKeyPairs(params *dagconfig.Params, mnemonics []string, cmdLinePassword string, isMultisig bool) ( +func encryptedMnemonicExtendedPublicKeyPairs(params *dagconfig.Params, mnemonics []string, cmdLinePassword string, isMultisig bool, version uint32) ( encryptedPrivateKeys []*EncryptedMnemonic, extendedPublicKeys []string, err error) { password := []byte(cmdLinePassword) if len(password) == 0 { @@ -65,7 +65,7 @@ func encryptedMnemonicExtendedPublicKeyPairs(params *dagconfig.Params, mnemonics extendedPublicKeys = make([]string, 0, len(mnemonics)) for _, mnemonic := range mnemonics { - extendedPublicKey, err := libkarlsenwallet.MasterPublicKeyFromMnemonic(params, mnemonic, isMultisig) + extendedPublicKey, err := libkarlsenwallet.MasterPublicKeyFromMnemonic(params, mnemonic, isMultisig, version) if err != nil { return nil, nil, err } diff --git a/cmd/karlsenwallet/keys/keys.go b/cmd/karlsenwallet/keys/keys.go index 73e7ef2214..8ab0015d8a 100644 --- a/cmd/karlsenwallet/keys/keys.go +++ b/cmd/karlsenwallet/keys/keys.go @@ -27,7 +27,7 @@ var ( ) // LastVersion is the most up to date file format version -const LastVersion = 1 +const LastVersion = 2 func defaultKeysFile(netParams *dagconfig.Params) string { return filepath.Join(defaultAppDir, netParams.Name, "keys.json") @@ -95,7 +95,7 @@ func (d *File) toJSON() *keysFileJSON { // NewFileFromMnemonic generates a new File from the given mnemonic string func NewFileFromMnemonic(params *dagconfig.Params, mnemonic string, password string) (*File, error) { encryptedMnemonics, extendedPublicKeys, err := - encryptedMnemonicExtendedPublicKeyPairs(params, []string{mnemonic}, password, false) + encryptedMnemonicExtendedPublicKeyPairs(params, []string{mnemonic}, password, false, LastVersion) if err != nil { return nil, err } diff --git a/cmd/karlsenwallet/libkarlsenwallet/bip39.go b/cmd/karlsenwallet/libkarlsenwallet/bip39.go index 031f8c0932..cd4b2ae6bd 100644 --- a/cmd/karlsenwallet/libkarlsenwallet/bip39.go +++ b/cmd/karlsenwallet/libkarlsenwallet/bip39.go @@ -22,22 +22,30 @@ const ( // Note: this is not entirely compatible to BIP 45 since // BIP 45 doesn't have a coin type in its derivation path. MultiSigPurpose = 45 - // TODO: Register the coin type in https://github.com/satoshilabs/slips/blob/master/slip-0044.md + // Registered in https://github.com/satoshilabs/slips/blob/master/slip-0044.md CoinType = 121337 + // Wallet version 1 coin type + CoinTypeV1 = 111111 ) -func defaultPath(isMultisig bool) string { +func defaultPath(isMultisig bool, version uint32) string { purpose := SingleSignerPurpose if isMultisig { purpose = MultiSigPurpose } + // Note: this is needed because initial fork was created + // without changing the coin type in derivation path. + if version == 1 { + return fmt.Sprintf("m/%d'/%d'/0'", purpose, CoinTypeV1) + } + return fmt.Sprintf("m/%d'/%d'/0'", purpose, CoinType) } // MasterPublicKeyFromMnemonic returns the master public key with the correct derivation for the given mnemonic. -func MasterPublicKeyFromMnemonic(params *dagconfig.Params, mnemonic string, isMultisig bool) (string, error) { - path := defaultPath(isMultisig) +func MasterPublicKeyFromMnemonic(params *dagconfig.Params, mnemonic string, isMultisig bool, version uint32) (string, error) { + path := defaultPath(isMultisig, version) extendedKey, err := extendedKeyFromMnemonicAndPath(mnemonic, path, params) if err != nil { return "", err diff --git a/cmd/karlsenwallet/libkarlsenwallet/sign.go b/cmd/karlsenwallet/libkarlsenwallet/sign.go index fca6979b0e..2df589d6f9 100644 --- a/cmd/karlsenwallet/libkarlsenwallet/sign.go +++ b/cmd/karlsenwallet/libkarlsenwallet/sign.go @@ -28,14 +28,14 @@ func rawTxInSignature(extendedKey *bip32.ExtendedKey, tx *externalapi.DomainTran } // Sign signs the transaction with the given private keys -func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, ecdsa bool) ([]byte, error) { +func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, ecdsa bool, version uint32) ([]byte, error) { partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx) if err != nil { return nil, err } for _, mnemonic := range mnemonics { - err = sign(params, mnemonic, partiallySignedTransaction, ecdsa) + err = sign(params, mnemonic, partiallySignedTransaction, ecdsa, version) if err != nil { return nil, err } @@ -43,7 +43,7 @@ func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, e return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction) } -func sign(params *dagconfig.Params, mnemonic string, partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) error { +func sign(params *dagconfig.Params, mnemonic string, partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool, version uint32) error { if isTransactionFullySigned(partiallySignedTransaction) { return nil } @@ -63,7 +63,7 @@ func sign(params *dagconfig.Params, mnemonic string, partiallySignedTransaction signed := false for i, partiallySignedInput := range partiallySignedTransaction.PartiallySignedInputs { isMultisig := len(partiallySignedInput.PubKeySignaturePairs) > 1 - path := defaultPath(isMultisig) + path := defaultPath(isMultisig, version) extendedKey, err := extendedKeyFromMnemonicAndPath(mnemonic, path, params) if err != nil { return err diff --git a/cmd/karlsenwallet/send.go b/cmd/karlsenwallet/send.go index eafc0344d5..bf6a606f30 100644 --- a/cmd/karlsenwallet/send.go +++ b/cmd/karlsenwallet/send.go @@ -68,7 +68,7 @@ func send(conf *sendConfig) error { signedTransactions := make([][]byte, len(createUnsignedTransactionsResponse.UnsignedTransactions)) for i, unsignedTransaction := range createUnsignedTransactionsResponse.UnsignedTransactions { - signedTransaction, err := libkarlsenwallet.Sign(conf.NetParams(), mnemonics, unsignedTransaction, keysFile.ECDSA) + signedTransaction, err := libkarlsenwallet.Sign(conf.NetParams(), mnemonics, unsignedTransaction, keysFile.ECDSA, keysFile.Version) if err != nil { return err } diff --git a/cmd/karlsenwallet/sign.go b/cmd/karlsenwallet/sign.go index 3554f89094..15f2d25eed 100644 --- a/cmd/karlsenwallet/sign.go +++ b/cmd/karlsenwallet/sign.go @@ -48,7 +48,7 @@ func sign(conf *signConfig) error { updatedPartiallySignedTransactions := make([][]byte, len(partiallySignedTransactions)) for i, partiallySignedTransaction := range partiallySignedTransactions { updatedPartiallySignedTransactions[i], err = - libkarlsenwallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA) + libkarlsenwallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA, keysFile.Version) if err != nil { return err } From b8decf0d0c93ec353341e3f4cf8a7b4704681674 Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 13:01:02 +0100 Subject: [PATCH 2/8] Show used wallet version and warning if legacy is used --- cmd/karlsenwallet/daemon/server/server.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/karlsenwallet/daemon/server/server.go b/cmd/karlsenwallet/daemon/server/server.go index 654c78b26b..2780ec962b 100644 --- a/cmd/karlsenwallet/daemon/server/server.go +++ b/cmd/karlsenwallet/daemon/server/server.go @@ -82,6 +82,18 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri return err } + // Show wallet version + log.Infof("Wallet version %d found", keysFile.Version) + + // Show warning if old wallet version is used. + if keysFile.Version == 1 { + log.Infof("---"); + log.Infof("For future compatibility it is...") + log.Infof("highly recommended to create a...") + log.Infof("new one and transfer KLS to it.") + log.Infof("---"); + } + serverInstance := &server{ rpcClient: rpcClient, params: params, From 1c14b56deb308df86654c401cae8d0072c5ddea2 Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 18:32:03 +0100 Subject: [PATCH 3/8] Fixed Go standard formatting --- cmd/karlsenwallet/daemon/server/server.go | 4 ++-- cmd/karlsenwallet/libkarlsenwallet/bip39.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/karlsenwallet/daemon/server/server.go b/cmd/karlsenwallet/daemon/server/server.go index 2780ec962b..b7b4126b1f 100644 --- a/cmd/karlsenwallet/daemon/server/server.go +++ b/cmd/karlsenwallet/daemon/server/server.go @@ -87,11 +87,11 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri // Show warning if old wallet version is used. if keysFile.Version == 1 { - log.Infof("---"); + log.Infof("---") log.Infof("For future compatibility it is...") log.Infof("highly recommended to create a...") log.Infof("new one and transfer KLS to it.") - log.Infof("---"); + log.Infof("---") } serverInstance := &server{ diff --git a/cmd/karlsenwallet/libkarlsenwallet/bip39.go b/cmd/karlsenwallet/libkarlsenwallet/bip39.go index cd4b2ae6bd..5e32a5b9fb 100644 --- a/cmd/karlsenwallet/libkarlsenwallet/bip39.go +++ b/cmd/karlsenwallet/libkarlsenwallet/bip39.go @@ -35,7 +35,7 @@ func defaultPath(isMultisig bool, version uint32) string { } // Note: this is needed because initial fork was created - // without changing the coin type in derivation path. + // without changing the coin type in derivation path. if version == 1 { return fmt.Sprintf("m/%d'/%d'/0'", purpose, CoinTypeV1) } From c5c65bc6d865cbd1ba163db84fa8a7a198f7a651 Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 18:34:52 +0100 Subject: [PATCH 4/8] Updated version to 1.2.0 --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 01a8c3dbbb..619e8f3f57 100644 --- a/version/version.go +++ b/version/version.go @@ -10,7 +10,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs const ( appMajor uint = 1 - appMinor uint = 1 + appMinor uint = 2 appPatch uint = 0 ) From b7eeb18950b427f9605e1758a57cb6b74e20147c Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 23:02:28 +0100 Subject: [PATCH 5/8] Added testutil to walk through all available derivation paths --- .../utils/testutils/for_all_paths.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 domain/consensus/utils/testutils/for_all_paths.go diff --git a/domain/consensus/utils/testutils/for_all_paths.go b/domain/consensus/utils/testutils/for_all_paths.go new file mode 100644 index 0000000000..bff0a73926 --- /dev/null +++ b/domain/consensus/utils/testutils/for_all_paths.go @@ -0,0 +1,19 @@ +package testutils + +import ( + "fmt" + "testing" + + "github.com/karlsen-network/karlsend/cmd/karlsenwallet/keys" +) + +// ForAllPaths runs the passwd testFunc with all available derivation paths +func ForAllPaths(t *testing.T, testFunc func(*testing.T, uint32)) { + for i := uint32(1) ; i <= keys.LastVersion ; i++ { + version := fmt.Sprintf("v%d", i) + t.Run(version, func(t *testing.T) { + t.Logf("Running test for wallet version %d", i) + testFunc(t, i) + }) + } +} From f93f5aa132b93b18f2e538cc669cbd895821721e Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 23:15:19 +0100 Subject: [PATCH 6/8] Added transaction and split transaction test case for v1 and v2 wallet --- .../daemon/server/split_transaction_test.go | 92 +-- .../libkarlsenwallet/transaction_test.go | 740 +++++++++--------- 2 files changed, 420 insertions(+), 412 deletions(-) diff --git a/cmd/karlsenwallet/daemon/server/split_transaction_test.go b/cmd/karlsenwallet/daemon/server/split_transaction_test.go index 8958285ab5..fda06e4173 100644 --- a/cmd/karlsenwallet/daemon/server/split_transaction_test.go +++ b/cmd/karlsenwallet/daemon/server/split_transaction_test.go @@ -21,53 +21,55 @@ import ( ) func TestEstimateMassAfterSignatures(t *testing.T) { - testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { - unsignedTransactionBytes, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig) - defer teardown(false) - - serverInstance := &server{ - params: params, - keysFile: &keys.File{MinimumSignatures: 2}, - shutdown: make(chan struct{}), - addressSet: make(walletAddressSet), - txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp), - } - - unsignedTransaction, err := serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes) - if err != nil { - t.Fatalf("Error deserializing unsignedTransaction: %s", err) - } - - estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction) - if err != nil { - t.Fatalf("Error from estimateMassAfterSignatures: %s", err) - } - - signedTxStep1Bytes, err := libkarlsenwallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false) - if err != nil { - t.Fatalf("Sign: %+v", err) - } - - signedTxStep2Bytes, err := libkarlsenwallet.Sign(params, mnemonics[1:2], signedTxStep1Bytes, false) - if err != nil { - t.Fatalf("Sign: %+v", err) - } - - extractedSignedTx, err := libkarlsenwallet.ExtractTransaction(signedTxStep2Bytes, false) - if err != nil { - t.Fatalf("ExtractTransaction: %+v", err) - } - - actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx) - - if estimatedMassAfterSignatures != actualMassAfterSignatures { - t.Errorf("Estimated mass after signatures: %d but actually got %d", - estimatedMassAfterSignatures, actualMassAfterSignatures) - } + testutils.ForAllPaths(t, func(t *testing.T, version uint32) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + unsignedTransactionBytes, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig, version) + defer teardown(false) + + serverInstance := &server{ + params: params, + keysFile: &keys.File{MinimumSignatures: 2}, + shutdown: make(chan struct{}), + addressSet: make(walletAddressSet), + txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp), + } + + unsignedTransaction, err := serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes) + if err != nil { + t.Fatalf("Error deserializing unsignedTransaction: %s", err) + } + + estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction) + if err != nil { + t.Fatalf("Error from estimateMassAfterSignatures: %s", err) + } + + signedTxStep1Bytes, err := libkarlsenwallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false, version) + if err != nil { + t.Fatalf("Sign: %+v", err) + } + + signedTxStep2Bytes, err := libkarlsenwallet.Sign(params, mnemonics[1:2], signedTxStep1Bytes, false, version) + if err != nil { + t.Fatalf("Sign: %+v", err) + } + + extractedSignedTx, err := libkarlsenwallet.ExtractTransaction(signedTxStep2Bytes, false) + if err != nil { + t.Fatalf("ExtractTransaction: %+v", err) + } + + actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx) + + if estimatedMassAfterSignatures != actualMassAfterSignatures { + t.Errorf("Estimated mass after signatures: %d but actually got %d", + estimatedMassAfterSignatures, actualMassAfterSignatures) + } + }) }) } -func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) ( +func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config, version uint32) ( []byte, []string, *dagconfig.Params, func(keepDataDir bool)) { consensusConfig.BlockCoinbaseMaturity = 0 @@ -88,7 +90,7 @@ func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *c t.Fatalf("CreateMnemonic: %+v", err) } - publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true) + publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true, version) if err != nil { t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) } diff --git a/cmd/karlsenwallet/libkarlsenwallet/transaction_test.go b/cmd/karlsenwallet/libkarlsenwallet/transaction_test.go index b820174002..8af1b79d3a 100644 --- a/cmd/karlsenwallet/libkarlsenwallet/transaction_test.go +++ b/cmd/karlsenwallet/libkarlsenwallet/transaction_test.go @@ -28,166 +28,303 @@ func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) { } func TestMultisig(t *testing.T) { - testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { - params := &consensusConfig.Params - forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) { - consensusConfig.BlockCoinbaseMaturity = 0 - tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig") - if err != nil { - t.Fatalf("Error setting up tc: %+v", err) - } - defer teardown(false) - - const numKeys = 3 - mnemonics := make([]string, numKeys) - publicKeys := make([]string, numKeys) - for i := 0; i < numKeys; i++ { - var err error - mnemonics[i], err = libkarlsenwallet.CreateMnemonic() + testutils.ForAllPaths(t, func(t *testing.T, version uint32) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + params := &consensusConfig.Params + forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) { + consensusConfig.BlockCoinbaseMaturity = 0 + tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig") if err != nil { - t.Fatalf("CreateMnemonic: %+v", err) + t.Fatalf("Error setting up tc: %+v", err) + } + defer teardown(false) + + const numKeys = 3 + mnemonics := make([]string, numKeys) + publicKeys := make([]string, numKeys) + for i := 0; i < numKeys; i++ { + var err error + mnemonics[i], err = libkarlsenwallet.CreateMnemonic() + if err != nil { + t.Fatalf("CreateMnemonic: %+v", err) + } + + publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true, version) + if err != nil { + t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) + } } - publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true) + const minimumSignatures = 2 + path := "m/1/2/3" + address, err := libkarlsenwallet.Address(params, publicKeys, minimumSignatures, path, ecdsa) if err != nil { - t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) + t.Fatalf("Address: %+v", err) } - } - const minimumSignatures = 2 - path := "m/1/2/3" - address, err := libkarlsenwallet.Address(params, publicKeys, minimumSignatures, path, ecdsa) - if err != nil { - t.Fatalf("Address: %+v", err) - } - - if _, ok := address.(*util.AddressScriptHash); !ok { - t.Fatalf("The address is of unexpected type") - } + if _, ok := address.(*util.AddressScriptHash); !ok { + t.Fatalf("The address is of unexpected type") + } - scriptPublicKey, err := txscript.PayToAddrScript(address) - if err != nil { - t.Fatalf("PayToAddrScript: %+v", err) - } + scriptPublicKey, err := txscript.PayToAddrScript(address) + if err != nil { + t.Fatalf("PayToAddrScript: %+v", err) + } - coinbaseData := &externalapi.DomainCoinbaseData{ - ScriptPublicKey: scriptPublicKey, - ExtraData: nil, - } + coinbaseData := &externalapi.DomainCoinbaseData{ + ScriptPublicKey: scriptPublicKey, + ExtraData: nil, + } - fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } + fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } - block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } + block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } - block1, _, err := tc.GetBlock(block1Hash) - if err != nil { - t.Fatalf("GetBlock: %+v", err) - } + block1, _, err := tc.GetBlock(block1Hash) + if err != nil { + t.Fatalf("GetBlock: %+v", err) + } - block1Tx := block1.Transactions[0] - block1TxOut := block1Tx.Outputs[0] - selectedUTXOs := []*libkarlsenwallet.UTXO{ - { - Outpoint: &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), - Index: 0, + block1Tx := block1.Transactions[0] + block1TxOut := block1Tx.Outputs[0] + selectedUTXOs := []*libkarlsenwallet.UTXO{ + { + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0), + DerivationPath: path, }, - UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0), - DerivationPath: path, - }, - } + } - unsignedTransaction, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, - []*libkarlsenwallet.Payment{{ - Address: address, - Amount: 10, - }}, selectedUTXOs) - if err != nil { - t.Fatalf("CreateUnsignedTransactions: %+v", err) - } + unsignedTransaction, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, + []*libkarlsenwallet.Payment{{ + Address: address, + Amount: 10, + }}, selectedUTXOs) + if err != nil { + t.Fatalf("CreateUnsignedTransactions: %+v", err) + } - isFullySigned, err := libkarlsenwallet.IsTransactionFullySigned(unsignedTransaction) - if err != nil { - t.Fatalf("IsTransactionFullySigned: %+v", err) - } + isFullySigned, err := libkarlsenwallet.IsTransactionFullySigned(unsignedTransaction) + if err != nil { + t.Fatalf("IsTransactionFullySigned: %+v", err) + } - if isFullySigned { - t.Fatalf("Transaction is not expected to be signed") - } + if isFullySigned { + t.Fatalf("Transaction is not expected to be signed") + } - _, err = libkarlsenwallet.ExtractTransaction(unsignedTransaction, ecdsa) - if err == nil || !strings.Contains(err.Error(), fmt.Sprintf("missing %d signatures", minimumSignatures)) { - t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction") - } + _, err = libkarlsenwallet.ExtractTransaction(unsignedTransaction, ecdsa) + if err == nil || !strings.Contains(err.Error(), fmt.Sprintf("missing %d signatures", minimumSignatures)) { + t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction") + } - signedTxStep1, err := libkarlsenwallet.Sign(params, mnemonics[:1], unsignedTransaction, ecdsa) - if err != nil { - t.Fatalf("Sign: %+v", err) - } + signedTxStep1, err := libkarlsenwallet.Sign(params, mnemonics[:1], unsignedTransaction, ecdsa, version) + if err != nil { + t.Fatalf("Sign: %+v", err) + } - isFullySigned, err = libkarlsenwallet.IsTransactionFullySigned(signedTxStep1) - if err != nil { - t.Fatalf("IsTransactionFullySigned: %+v", err) - } + isFullySigned, err = libkarlsenwallet.IsTransactionFullySigned(signedTxStep1) + if err != nil { + t.Fatalf("IsTransactionFullySigned: %+v", err) + } - if isFullySigned { - t.Fatalf("Transaction is not expected to be fully signed") - } + if isFullySigned { + t.Fatalf("Transaction is not expected to be fully signed") + } - signedTxStep2, err := libkarlsenwallet.Sign(params, mnemonics[1:2], signedTxStep1, ecdsa) - if err != nil { - t.Fatalf("Sign: %+v", err) - } + signedTxStep2, err := libkarlsenwallet.Sign(params, mnemonics[1:2], signedTxStep1, ecdsa, version) + if err != nil { + t.Fatalf("Sign: %+v", err) + } - extractedSignedTxStep2, err := libkarlsenwallet.ExtractTransaction(signedTxStep2, ecdsa) - if err != nil { - t.Fatalf("ExtractTransaction: %+v", err) - } + extractedSignedTxStep2, err := libkarlsenwallet.ExtractTransaction(signedTxStep2, ecdsa) + if err != nil { + t.Fatalf("ExtractTransaction: %+v", err) + } - signedTxOneStep, err := libkarlsenwallet.Sign(params, mnemonics[:2], unsignedTransaction, ecdsa) - if err != nil { - t.Fatalf("Sign: %+v", err) - } + signedTxOneStep, err := libkarlsenwallet.Sign(params, mnemonics[:2], unsignedTransaction, ecdsa, version) + if err != nil { + t.Fatalf("Sign: %+v", err) + } - extractedSignedTxOneStep, err := libkarlsenwallet.ExtractTransaction(signedTxOneStep, ecdsa) - if err != nil { - t.Fatalf("ExtractTransaction: %+v", err) - } + extractedSignedTxOneStep, err := libkarlsenwallet.ExtractTransaction(signedTxOneStep, ecdsa) + if err != nil { + t.Fatalf("ExtractTransaction: %+v", err) + } - // We check IDs instead of comparing the actual transactions because the actual transactions have different - // signature scripts due to non deterministic signature scheme. - if !consensushashing.TransactionID(extractedSignedTxStep2).Equal(consensushashing.TransactionID(extractedSignedTxOneStep)) { - t.Fatalf("Expected extractedSignedTxOneStep and extractedSignedTxStep2 IDs to be equal") - } + // We check IDs instead of comparing the actual transactions because the actual transactions have different + // signature scripts due to non deterministic signature scheme. + if !consensushashing.TransactionID(extractedSignedTxStep2).Equal(consensushashing.TransactionID(extractedSignedTxOneStep)) { + t.Fatalf("Expected extractedSignedTxOneStep and extractedSignedTxStep2 IDs to be equal") + } - _, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{extractedSignedTxStep2}) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } + _, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{extractedSignedTxStep2}) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } - addedUTXO := &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(extractedSignedTxStep2), - Index: 0, - } - if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { - t.Fatalf("Transaction wasn't accepted in the DAG") - } + addedUTXO := &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(extractedSignedTxStep2), + Index: 0, + } + if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { + t.Fatalf("Transaction wasn't accepted in the DAG") + } + }) }) }) } func TestP2PK(t *testing.T) { - testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { - params := &consensusConfig.Params - forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) { - consensusConfig.BlockCoinbaseMaturity = 0 - tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig") + testutils.ForAllPaths(t, func(t *testing.T, version uint32) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + params := &consensusConfig.Params + forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) { + consensusConfig.BlockCoinbaseMaturity = 0 + tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig") + if err != nil { + t.Fatalf("Error setting up tc: %+v", err) + } + defer teardown(false) + + const numKeys = 1 + mnemonics := make([]string, numKeys) + publicKeys := make([]string, numKeys) + for i := 0; i < numKeys; i++ { + var err error + mnemonics[i], err = libkarlsenwallet.CreateMnemonic() + if err != nil { + t.Fatalf("CreateMnemonic: %+v", err) + } + + publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], false, version) + if err != nil { + t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) + } + } + + const minimumSignatures = 1 + path := "m/1/2/3" + address, err := libkarlsenwallet.Address(params, publicKeys, minimumSignatures, path, ecdsa) + if err != nil { + t.Fatalf("Address: %+v", err) + } + + if ecdsa { + if _, ok := address.(*util.AddressPublicKeyECDSA); !ok { + t.Fatalf("The address is of unexpected type") + } + } else { + if _, ok := address.(*util.AddressPublicKey); !ok { + t.Fatalf("The address is of unexpected type") + } + } + + scriptPublicKey, err := txscript.PayToAddrScript(address) + if err != nil { + t.Fatalf("PayToAddrScript: %+v", err) + } + + coinbaseData := &externalapi.DomainCoinbaseData{ + ScriptPublicKey: scriptPublicKey, + ExtraData: nil, + } + + fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + block1, _, err := tc.GetBlock(block1Hash) + if err != nil { + t.Fatalf("GetBlock: %+v", err) + } + + block1Tx := block1.Transactions[0] + block1TxOut := block1Tx.Outputs[0] + selectedUTXOs := []*libkarlsenwallet.UTXO{ + { + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0), + DerivationPath: path, + }, + } + + unsignedTransaction, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, + []*libkarlsenwallet.Payment{{ + Address: address, + Amount: 10, + }}, selectedUTXOs) + if err != nil { + t.Fatalf("CreateUnsignedTransactions: %+v", err) + } + + isFullySigned, err := libkarlsenwallet.IsTransactionFullySigned(unsignedTransaction) + if err != nil { + t.Fatalf("IsTransactionFullySigned: %+v", err) + } + + if isFullySigned { + t.Fatalf("Transaction is not expected to be signed") + } + + _, err = libkarlsenwallet.ExtractTransaction(unsignedTransaction, ecdsa) + if err == nil || !strings.Contains(err.Error(), "missing signature") { + t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction") + } + + signedTx, err := libkarlsenwallet.Sign(params, mnemonics, unsignedTransaction, ecdsa, version) + if err != nil { + t.Fatalf("Sign: %+v", err) + } + + tx, err := libkarlsenwallet.ExtractTransaction(signedTx, ecdsa) + if err != nil { + t.Fatalf("ExtractTransaction: %+v", err) + } + + _, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{tx}) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + addedUTXO := &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(tx), + Index: 0, + } + if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { + t.Fatalf("Transaction wasn't accepted in the DAG") + } + }) + }) + }) +} + +func TestMaxSompi(t *testing.T) { + testutils.ForAllPaths(t, func(t *testing.T, version uint32) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + params := &consensusConfig.Params + cfg := *consensusConfig + cfg.BlockCoinbaseMaturity = 0 + cfg.PreDeflationaryPhaseBaseSubsidy = 20e6 * constants.SompiPerKarlsen + tc, teardown, err := consensus.NewFactory().NewTestConsensus(&cfg, "TestMaxSompi") if err != nil { t.Fatalf("Error setting up tc: %+v", err) } @@ -203,7 +340,7 @@ func TestP2PK(t *testing.T) { t.Fatalf("CreateMnemonic: %+v", err) } - publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], false) + publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&cfg.Params, mnemonics[i], false, version) if err != nil { t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) } @@ -211,21 +348,11 @@ func TestP2PK(t *testing.T) { const minimumSignatures = 1 path := "m/1/2/3" - address, err := libkarlsenwallet.Address(params, publicKeys, minimumSignatures, path, ecdsa) + address, err := libkarlsenwallet.Address(params, publicKeys, minimumSignatures, path, false) if err != nil { t.Fatalf("Address: %+v", err) } - if ecdsa { - if _, ok := address.(*util.AddressPublicKeyECDSA); !ok { - t.Fatalf("The address is of unexpected type") - } - } else { - if _, ok := address.(*util.AddressPublicKey); !ok { - t.Fatalf("The address is of unexpected type") - } - } - scriptPublicKey, err := txscript.PayToAddrScript(address) if err != nil { t.Fatalf("PayToAddrScript: %+v", err) @@ -236,12 +363,42 @@ func TestP2PK(t *testing.T) { ExtraData: nil, } - fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil) + fundingBlock1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{cfg.GenesisHash}, coinbaseData, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + fundingBlock2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock1Hash}, coinbaseData, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + fundingBlock3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock2Hash}, coinbaseData, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + fundingBlock4Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock3Hash}, coinbaseData, nil) if err != nil { t.Fatalf("AddBlock: %+v", err) } - block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil) + fundingBlock2, _, err := tc.GetBlock(fundingBlock2Hash) + if err != nil { + t.Fatalf("GetBlock: %+v", err) + } + + fundingBlock3, _, err := tc.GetBlock(fundingBlock3Hash) + if err != nil { + t.Fatalf("GetBlock: %+v", err) + } + + fundingBlock4, _, err := tc.GetBlock(fundingBlock4Hash) + if err != nil { + t.Fatalf("GetBlock: %+v", err) + } + + block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock4Hash}, nil, nil) if err != nil { t.Fatalf("AddBlock: %+v", err) } @@ -251,269 +408,118 @@ func TestP2PK(t *testing.T) { t.Fatalf("GetBlock: %+v", err) } - block1Tx := block1.Transactions[0] - block1TxOut := block1Tx.Outputs[0] - selectedUTXOs := []*libkarlsenwallet.UTXO{ + txOut1 := fundingBlock2.Transactions[0].Outputs[0] + txOut2 := fundingBlock3.Transactions[0].Outputs[0] + txOut3 := fundingBlock4.Transactions[0].Outputs[0] + txOut4 := block1.Transactions[0].Outputs[0] + selectedUTXOsForTxWithLargeInputAmount := []*libkarlsenwallet.UTXO{ { Outpoint: &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), + TransactionID: *consensushashing.TransactionID(fundingBlock2.Transactions[0]), Index: 0, }, - UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0), + UTXOEntry: utxo.NewUTXOEntry(txOut1.Value, txOut1.ScriptPublicKey, true, 0), + DerivationPath: path, + }, + { + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(fundingBlock3.Transactions[0]), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(txOut2.Value, txOut2.ScriptPublicKey, true, 0), DerivationPath: path, }, } - unsignedTransaction, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, + unsignedTxWithLargeInputAmount, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, []*libkarlsenwallet.Payment{{ Address: address, Amount: 10, - }}, selectedUTXOs) + }}, selectedUTXOsForTxWithLargeInputAmount) if err != nil { t.Fatalf("CreateUnsignedTransactions: %+v", err) } - isFullySigned, err := libkarlsenwallet.IsTransactionFullySigned(unsignedTransaction) + signedTxWithLargeInputAmount, err := libkarlsenwallet.Sign(params, mnemonics, unsignedTxWithLargeInputAmount, false, version) if err != nil { - t.Fatalf("IsTransactionFullySigned: %+v", err) + t.Fatalf("Sign: %+v", err) } - if isFullySigned { - t.Fatalf("Transaction is not expected to be signed") + txWithLargeInputAmount, err := libkarlsenwallet.ExtractTransaction(signedTxWithLargeInputAmount, false) + if err != nil { + t.Fatalf("ExtractTransaction: %+v", err) } - _, err = libkarlsenwallet.ExtractTransaction(unsignedTransaction, ecdsa) - if err == nil || !strings.Contains(err.Error(), "missing signature") { - t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction") + _, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{txWithLargeInputAmount}) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + addedUTXO1 := &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(txWithLargeInputAmount), + Index: 0, + } + if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO1) { + t.Fatalf("Transaction wasn't accepted in the DAG") } - signedTx, err := libkarlsenwallet.Sign(params, mnemonics, unsignedTransaction, ecdsa) + selectedUTXOsForTxWithLargeInputAndOutputAmount := []*libkarlsenwallet.UTXO{ + { + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(fundingBlock4.Transactions[0]), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(txOut3.Value, txOut3.ScriptPublicKey, true, 0), + DerivationPath: path, + }, + { + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(txOut4.Value, txOut4.ScriptPublicKey, true, 0), + DerivationPath: path, + }, + } + + unsignedTxWithLargeInputAndOutputAmount, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, + []*libkarlsenwallet.Payment{{ + Address: address, + Amount: 22e6 * constants.SompiPerKarlsen, + }}, selectedUTXOsForTxWithLargeInputAndOutputAmount) + if err != nil { + t.Fatalf("CreateUnsignedTransactions: %+v", err) + } + + signedTxWithLargeInputAndOutputAmount, err := libkarlsenwallet.Sign(params, mnemonics, unsignedTxWithLargeInputAndOutputAmount, false, version) if err != nil { t.Fatalf("Sign: %+v", err) } - tx, err := libkarlsenwallet.ExtractTransaction(signedTx, ecdsa) + txWithLargeInputAndOutputAmount, err := libkarlsenwallet.ExtractTransaction(signedTxWithLargeInputAndOutputAmount, false) if err != nil { t.Fatalf("ExtractTransaction: %+v", err) } - _, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{tx}) + // We're creating a new longer chain so we can double spend txWithLargeInputAmount + newChainRoot, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, nil) if err != nil { t.Fatalf("AddBlock: %+v", err) } - addedUTXO := &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(tx), - Index: 0, - } - if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { - t.Fatalf("Transaction wasn't accepted in the DAG") + _, virtualChangeSet, err = tc.AddBlock([]*externalapi.DomainHash{newChainRoot}, nil, []*externalapi.DomainTransaction{txWithLargeInputAndOutputAmount}) + if err != nil { + t.Fatalf("AddBlock: %+v", err) } - }) - }) -} -func TestMaxSompi(t *testing.T) { - testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { - params := &consensusConfig.Params - cfg := *consensusConfig - cfg.BlockCoinbaseMaturity = 0 - cfg.PreDeflationaryPhaseBaseSubsidy = 20e6 * constants.SompiPerKarlsen - tc, teardown, err := consensus.NewFactory().NewTestConsensus(&cfg, "TestMaxSompi") - if err != nil { - t.Fatalf("Error setting up tc: %+v", err) - } - defer teardown(false) - - const numKeys = 1 - mnemonics := make([]string, numKeys) - publicKeys := make([]string, numKeys) - for i := 0; i < numKeys; i++ { - var err error - mnemonics[i], err = libkarlsenwallet.CreateMnemonic() - if err != nil { - t.Fatalf("CreateMnemonic: %+v", err) + addedUTXO2 := &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(txWithLargeInputAndOutputAmount), + Index: 0, } - publicKeys[i], err = libkarlsenwallet.MasterPublicKeyFromMnemonic(&cfg.Params, mnemonics[i], false) - if err != nil { - t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) + if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO2) { + t.Fatalf("txWithLargeInputAndOutputAmount weren't accepted in the DAG") } - } - - const minimumSignatures = 1 - path := "m/1/2/3" - address, err := libkarlsenwallet.Address(params, publicKeys, minimumSignatures, path, false) - if err != nil { - t.Fatalf("Address: %+v", err) - } - - scriptPublicKey, err := txscript.PayToAddrScript(address) - if err != nil { - t.Fatalf("PayToAddrScript: %+v", err) - } - - coinbaseData := &externalapi.DomainCoinbaseData{ - ScriptPublicKey: scriptPublicKey, - ExtraData: nil, - } - - fundingBlock1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{cfg.GenesisHash}, coinbaseData, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - fundingBlock2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock1Hash}, coinbaseData, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - fundingBlock3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock2Hash}, coinbaseData, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - fundingBlock4Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock3Hash}, coinbaseData, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - fundingBlock2, _, err := tc.GetBlock(fundingBlock2Hash) - if err != nil { - t.Fatalf("GetBlock: %+v", err) - } - - fundingBlock3, _, err := tc.GetBlock(fundingBlock3Hash) - if err != nil { - t.Fatalf("GetBlock: %+v", err) - } - - fundingBlock4, _, err := tc.GetBlock(fundingBlock4Hash) - if err != nil { - t.Fatalf("GetBlock: %+v", err) - } - - block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock4Hash}, nil, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - block1, _, err := tc.GetBlock(block1Hash) - if err != nil { - t.Fatalf("GetBlock: %+v", err) - } - - txOut1 := fundingBlock2.Transactions[0].Outputs[0] - txOut2 := fundingBlock3.Transactions[0].Outputs[0] - txOut3 := fundingBlock4.Transactions[0].Outputs[0] - txOut4 := block1.Transactions[0].Outputs[0] - selectedUTXOsForTxWithLargeInputAmount := []*libkarlsenwallet.UTXO{ - { - Outpoint: &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(fundingBlock2.Transactions[0]), - Index: 0, - }, - UTXOEntry: utxo.NewUTXOEntry(txOut1.Value, txOut1.ScriptPublicKey, true, 0), - DerivationPath: path, - }, - { - Outpoint: &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(fundingBlock3.Transactions[0]), - Index: 0, - }, - UTXOEntry: utxo.NewUTXOEntry(txOut2.Value, txOut2.ScriptPublicKey, true, 0), - DerivationPath: path, - }, - } - - unsignedTxWithLargeInputAmount, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, - []*libkarlsenwallet.Payment{{ - Address: address, - Amount: 10, - }}, selectedUTXOsForTxWithLargeInputAmount) - if err != nil { - t.Fatalf("CreateUnsignedTransactions: %+v", err) - } - - signedTxWithLargeInputAmount, err := libkarlsenwallet.Sign(params, mnemonics, unsignedTxWithLargeInputAmount, false) - if err != nil { - t.Fatalf("Sign: %+v", err) - } - - txWithLargeInputAmount, err := libkarlsenwallet.ExtractTransaction(signedTxWithLargeInputAmount, false) - if err != nil { - t.Fatalf("ExtractTransaction: %+v", err) - } - - _, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{txWithLargeInputAmount}) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - addedUTXO1 := &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(txWithLargeInputAmount), - Index: 0, - } - if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO1) { - t.Fatalf("Transaction wasn't accepted in the DAG") - } - - selectedUTXOsForTxWithLargeInputAndOutputAmount := []*libkarlsenwallet.UTXO{ - { - Outpoint: &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(fundingBlock4.Transactions[0]), - Index: 0, - }, - UTXOEntry: utxo.NewUTXOEntry(txOut3.Value, txOut3.ScriptPublicKey, true, 0), - DerivationPath: path, - }, - { - Outpoint: &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), - Index: 0, - }, - UTXOEntry: utxo.NewUTXOEntry(txOut4.Value, txOut4.ScriptPublicKey, true, 0), - DerivationPath: path, - }, - } - - unsignedTxWithLargeInputAndOutputAmount, err := libkarlsenwallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, - []*libkarlsenwallet.Payment{{ - Address: address, - Amount: 22e6 * constants.SompiPerKarlsen, - }}, selectedUTXOsForTxWithLargeInputAndOutputAmount) - if err != nil { - t.Fatalf("CreateUnsignedTransactions: %+v", err) - } - - signedTxWithLargeInputAndOutputAmount, err := libkarlsenwallet.Sign(params, mnemonics, unsignedTxWithLargeInputAndOutputAmount, false) - if err != nil { - t.Fatalf("Sign: %+v", err) - } - - txWithLargeInputAndOutputAmount, err := libkarlsenwallet.ExtractTransaction(signedTxWithLargeInputAndOutputAmount, false) - if err != nil { - t.Fatalf("ExtractTransaction: %+v", err) - } - - // We're creating a new longer chain so we can double spend txWithLargeInputAmount - newChainRoot, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, nil) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - _, virtualChangeSet, err = tc.AddBlock([]*externalapi.DomainHash{newChainRoot}, nil, []*externalapi.DomainTransaction{txWithLargeInputAndOutputAmount}) - if err != nil { - t.Fatalf("AddBlock: %+v", err) - } - - addedUTXO2 := &externalapi.DomainOutpoint{ - TransactionID: *consensushashing.TransactionID(txWithLargeInputAndOutputAmount), - Index: 0, - } - - if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO2) { - t.Fatalf("txWithLargeInputAndOutputAmount weren't accepted in the DAG") - } + }) }) } From c31daa11a4d03dfcb0d92ebc89858a044d8b9dfa Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 23:24:54 +0100 Subject: [PATCH 7/8] Updated for next release --- changelog.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/changelog.txt b/changelog.txt index 319f42b5e8..55a5303e29 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,16 @@ +Karlsend v1.2.0 - 2024-03-07 +============================ + +* Fixed karlsend and karlsenminer Go traces +* Fixed all stability and integration tests +* Fixed crash in karlsenminer +* Fixed race in gRPC client send/recv/close handler +* Switched cointype in bip39 derivation to 121337 +* Added support for derivation path from Kaspa (wallet v1) and Karlsen (wallet v2) +* Added code of conduct +* Changed karlsenwallet listen port from 8082 to 9182 +* Updated Go modules and dependencies + Karlsend v1.1.0 - 2023-12-25 ============================ From d4a936f7a65b0b62848c98c5da4c241eb294674d Mon Sep 17 00:00:00 2001 From: Lemois 1337 Date: Wed, 6 Mar 2024 23:39:13 +0100 Subject: [PATCH 8/8] Fixed a typo and again, make go fmt happy --- domain/consensus/utils/testutils/for_all_paths.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain/consensus/utils/testutils/for_all_paths.go b/domain/consensus/utils/testutils/for_all_paths.go index bff0a73926..ffb9cec267 100644 --- a/domain/consensus/utils/testutils/for_all_paths.go +++ b/domain/consensus/utils/testutils/for_all_paths.go @@ -7,9 +7,9 @@ import ( "github.com/karlsen-network/karlsend/cmd/karlsenwallet/keys" ) -// ForAllPaths runs the passwd testFunc with all available derivation paths +// ForAllPaths runs the passed testFunc with all available derivation paths func ForAllPaths(t *testing.T, testFunc func(*testing.T, uint32)) { - for i := uint32(1) ; i <= keys.LastVersion ; i++ { + for i := uint32(1); i <= keys.LastVersion; i++ { version := fmt.Sprintf("v%d", i) t.Run(version, func(t *testing.T) { t.Logf("Running test for wallet version %d", i)