Ori Newman 4658f9d05c
Implement BIP 39 and HD wallet features (#1705)
* Naive bip39 with address reuse

* Avoid address reuse in libkaspawallet

* Add wallet daemon

* Use daemon everywhere

* Add forceOverride

* Make CreateUnsignedTransaction endpoint receive amount in sompis

* Collect close UTXOs

* Filter out non-spendable UTXOs from selectUTXOs

* Use different paths for multisig and non multisig

* Fix tests to use non zero path

* Fix multisig cosigner index detection

* Add comments

* Fix dump_unencrypted_data.go according to bip39 and bip32

* Fix wrong derivation path for multisig on wallet creation

* Remove IsSynced endpoint and add validation if wallet is synced for the relevant endpoints

* Rename server address to daemon address

* Fix capacity for extendedPublicKeys

* Use ReadBytes instead of ReadLine

* Add validation when importing

* Increment before using index value, and use it as is

* Save keys file exactly where needed

* Use %+v printErrorAndExit

* Remove redundant consts

* Rnemae collectCloseUTXOs and collectFarUTXOs

* Move typedefs around

* Add comment to addressesToQuery

* Update collectUTXOsFromRecentAddresses comment about locks

* Split collectUTXOs to small functions

* Add sanity check

* Add addEntryToUTXOSet function

* Change validateIsSynced to isSynced

* Simplify createKeyPairsFromFunction logic

* Rename .Sync() to .Save()

* Fix typo

* Create bip39BitSize const

* Add consts to purposes

* Add multisig check for 'send'

* Rename updatedPSTxBytes to partiallySignedTransaction

* Change collectUTXOsFromFarAddresses's comment

* Use setters for last used indexes

* Don't use the pstx acronym

* Fix SetPath

* Remove spaces when reading lines

* Fix walletserver to daemonaddress

* Fix isUTXOSpendable to use DAA score

Co-authored-by: Svarog <feanorr@gmail.com>
2021-05-19 10:03:23 +03:00

169 lines
5.0 KiB
Go

package libkaspawallet
import (
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"math"
"sort"
"strings"
)
// CreateKeyPair generates a private-public key pair
func CreateKeyPair(ecdsa bool) ([]byte, []byte, error) {
if ecdsa {
return createKeyPairECDSA()
}
return createKeyPair()
}
func createKeyPair() ([]byte, []byte, error) {
keyPair, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate private key")
}
publicKey, err := keyPair.SchnorrPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
}
return keyPair.SerializePrivateKey()[:], publicKeySerialized[:], nil
}
func createKeyPairECDSA() ([]byte, []byte, error) {
keyPair, err := secp256k1.GenerateECDSAPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate private key")
}
publicKey, err := keyPair.ECDSAPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
}
return keyPair.Serialize()[:], publicKeySerialized[:], nil
}
// PublicKeyFromPrivateKey returns the public key associated with a private key
func PublicKeyFromPrivateKey(privateKeyBytes []byte) ([]byte, error) {
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKeyBytes)
if err != nil {
return nil, errors.Wrap(err, "Failed to deserialize private key")
}
publicKey, err := keyPair.SchnorrPublicKey()
if err != nil {
return nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, errors.Wrap(err, "Failed to serialize public key")
}
return publicKeySerialized[:], nil
}
// Address returns the address associated with the given public keys and minimum signatures parameters.
func Address(params *dagconfig.Params, extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) (util.Address, error) {
sortPublicKeys(extendedPublicKeys)
if uint32(len(extendedPublicKeys)) < minimumSignatures {
return nil, errors.Errorf("The minimum amount of signatures (%d) is greater than the amount of "+
"provided public keys (%d)", minimumSignatures, len(extendedPublicKeys))
}
if len(extendedPublicKeys) == 1 {
return p2pkAddress(params, extendedPublicKeys[0], path, ecdsa)
}
redeemScript, err := multiSigRedeemScript(extendedPublicKeys, minimumSignatures, path, ecdsa)
if err != nil {
return nil, err
}
return util.NewAddressScriptHash(redeemScript, params.Prefix)
}
func p2pkAddress(params *dagconfig.Params, extendedPublicKey string, path string, ecdsa bool) (util.Address, error) {
extendedKey, err := bip32.DeserializeExtendedKey(extendedPublicKey)
if err != nil {
return nil, err
}
derivedKey, err := extendedKey.DeriveFromPath(path)
if err != nil {
return nil, err
}
publicKey, err := derivedKey.PublicKey()
if err != nil {
return nil, err
}
if ecdsa {
serializedECDSAPublicKey, err := publicKey.Serialize()
if err != nil {
return nil, err
}
return util.NewAddressPublicKeyECDSA(serializedECDSAPublicKey[:], params.Prefix)
}
schnorrPublicKey, err := publicKey.ToSchnorr()
if err != nil {
return nil, err
}
serializedSchnorrPublicKey, err := schnorrPublicKey.Serialize()
if err != nil {
return nil, err
}
return util.NewAddressPublicKey(serializedSchnorrPublicKey[:], params.Prefix)
}
func sortPublicKeys(extendedPublicKeys []string) {
sort.Slice(extendedPublicKeys, func(i, j int) bool {
return strings.Compare(extendedPublicKeys[i], extendedPublicKeys[j]) < 0
})
}
func cosignerIndex(extendedPublicKey string, sortedExtendedPublicKeys []string) (uint32, error) {
cosignerIndex := sort.SearchStrings(sortedExtendedPublicKeys, extendedPublicKey)
if cosignerIndex == len(sortedExtendedPublicKeys) {
return 0, errors.Errorf("couldn't find extended public key %s", extendedPublicKey)
}
return uint32(cosignerIndex), nil
}
// MinimumCosignerIndex returns the minimum index for the cosigner from the set of all extended public keys.
func MinimumCosignerIndex(cosignerExtendedPublicKeys, allExtendedPublicKeys []string) (uint32, error) {
allExtendedPublicKeysCopy := make([]string, len(allExtendedPublicKeys))
copy(allExtendedPublicKeysCopy, allExtendedPublicKeys)
sortPublicKeys(allExtendedPublicKeysCopy)
min := uint32(math.MaxUint32)
for _, extendedPublicKey := range cosignerExtendedPublicKeys {
cosignerIndex, err := cosignerIndex(extendedPublicKey, allExtendedPublicKeysCopy)
if err != nil {
return 0, err
}
if cosignerIndex < min {
min = cosignerIndex
}
}
return min, nil
}