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

100 lines
3.2 KiB
Go

package libkaspawallet
import (
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
)
func rawTxInSignature(extendedKey *bip32.ExtendedKey, tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues, ecdsa bool) ([]byte, error) {
privateKey := extendedKey.PrivateKey()
if ecdsa {
return txscript.RawTxInSignatureECDSA(tx, idx, hashType, privateKey, sighashReusedValues)
}
schnorrKeyPair, err := privateKey.ToSchnorr()
if err != nil {
return nil, err
}
return txscript.RawTxInSignature(tx, idx, hashType, schnorrKeyPair, sighashReusedValues)
}
// Sign signs the transaction with the given private keys
func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, ecdsa bool) ([]byte, error) {
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx)
if err != nil {
return nil, err
}
for _, mnemonic := range mnemonics {
err = sign(params, mnemonic, partiallySignedTransaction, ecdsa)
if err != nil {
return nil, err
}
}
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
}
func sign(params *dagconfig.Params, mnemonic string, partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) error {
if isTransactionFullySigned(partiallySignedTransaction) {
return nil
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, partiallySignedInput := range partiallySignedTransaction.PartiallySignedInputs {
prevOut := partiallySignedInput.PrevOutput
partiallySignedTransaction.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(
prevOut.Value,
prevOut.ScriptPublicKey,
false, // This is a fake value, because it's irrelevant for the signature
0, // This is a fake value, because it's irrelevant for the signature
)
}
signed := false
for i, partiallySignedInput := range partiallySignedTransaction.PartiallySignedInputs {
isMultisig := len(partiallySignedInput.PubKeySignaturePairs) > 1
path := defaultPath(isMultisig)
extendedKey, err := extendedKeyFromMnemonicAndPath(mnemonic, path, params)
if err != nil {
return err
}
derivedKey, err := extendedKey.DeriveFromPath(partiallySignedInput.DerivationPath)
if err != nil {
return err
}
derivedPublicKey, err := derivedKey.Public()
if err != nil {
return err
}
for _, pair := range partiallySignedInput.PubKeySignaturePairs {
if pair.ExtendedPublicKey == derivedPublicKey.String() {
pair.Signature, err = rawTxInSignature(derivedKey, partiallySignedTransaction.Tx, i, consensushashing.SigHashAll, sighashReusedValues, ecdsa)
if err != nil {
return err
}
signed = true
}
}
}
if !signed {
return errors.Errorf("Public key doesn't match any of the transaction public keys")
}
return nil
}