Skip to content

Commit

Permalink
Merge pull request #44 from lemois-1337/2024-bip39-cointype-fixes
Browse files Browse the repository at this point in the history
BIP39 Cointype Fixes
  • Loading branch information
lemois-1337 committed Mar 6, 2024
2 parents 713d2cc + d4a936f commit 3c63c5e
Show file tree
Hide file tree
Showing 16 changed files with 509 additions and 436 deletions.
13 changes: 13 additions & 0 deletions 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
============================

Expand Down
1 change: 1 addition & 0 deletions cmd/karlsenwallet/config.go
Expand Up @@ -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
}

Expand Down
18 changes: 15 additions & 3 deletions cmd/karlsenwallet/create.go
Expand Up @@ -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
Expand Down Expand Up @@ -65,7 +77,7 @@ func create(conf *createConfig) error {
}

file := keys.File{
Version: keys.LastVersion,
Version: version,
EncryptedMnemonics: encryptedMnemonics,
ExtendedPublicKeys: extendedPublicKeys,
MinimumSignatures: conf.MinimumSignatures,
Expand Down
12 changes: 12 additions & 0 deletions cmd/karlsenwallet/daemon/server/server.go
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion cmd/karlsenwallet/daemon/server/sign.go
Expand Up @@ -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
}
Expand Down
92 changes: 47 additions & 45 deletions cmd/karlsenwallet/daemon/server/split_transaction_test.go
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/karlsenwallet/dump_unencrypted_data.go
Expand Up @@ -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
}
Expand Down
12 changes: 6 additions & 6 deletions cmd/karlsenwallet/keys/create.go
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/karlsenwallet/keys/keys.go
Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand Down
16 changes: 12 additions & 4 deletions cmd/karlsenwallet/libkarlsenwallet/bip39.go
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions cmd/karlsenwallet/libkarlsenwallet/sign.go
Expand Up @@ -28,22 +28,22 @@ 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
}
}
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
}
Expand All @@ -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
Expand Down

0 comments on commit 3c63c5e

Please sign in to comment.