Svarog 639183ba0e
Add support for auto-compound in kaspawallet send (#1951)
* Add GetUTXOsByBalances command to rpc

* Fix wrong commands in GetBalanceByAddress

* Moved calculation of TransactionMass out of TransactionValidator, so t that it can be used in kaspawallet

* Allow CreateUnsignedTransaction to return multiple transactions

* Start working on split

* Implement maybeSplitTransactionInner

* estimateMassIncreaseForSignatures should multiply by the number of inputs

* Implement createSplitTransaction

* Implement mergeTransactions

* Broadcast all transaction, not only 1

* workaround missing UTXOEntry in partially signed transaction

* Bugfix in broadcast loop

* Add underscores in some constants

* Make all nets RelayNonStdTxs: false

* Change estimateMassIncreaseForSignatures to estimateMassAfterSignatures

* Allow situations where merge transaction doesn't have enough funds to pay fees

* Add comments

* A few of renames

* Handle missed errors

* Fix clone of PubKeySignaturePair  to properly clone nil signatures

* Add sanity check to make sure originalTransaction has exactly two outputs

* Re-use change address for splitAddress

* Add one more utxo if the total amount is smaller then what we need to send due to fees

* Fix off-by-1 error in splitTrasnaction

* Add a comment to maybeAutoCompoundTransaction

* Add comment on why we are increasing inputCountPerSplit

* Add comment explaining while originalTransaction has 1 or 2 outputs

* Move oneMoreUTXOForMergeTransaction to split_transaction.go

* Allow to add multiple utxos to pay fee for mergeTransactions, if needed

* calculate split input counts and sizes properly

* Allow multiple transactions inside the create-unsigned-transaction -> sign -> broadcast workflow

* Print the number of transaction which was sent, in case there were multiple

* Rename broadcastConfig.Transaction(File) to Transactions(File)

* Convert alreadySelectedUTXOs to a map

* Fix a typo

* Add comment explaining that we assume all inputs are the same

* Revert over-refactor of rename of config.Transaction -> config.Transactions

* Rename: inputPerSplitCount -> inputsPerSplitCount

* Add comment for splitAndInputPerSplitCounts

* Use createSplitTransaction to calculate the upper bound of mass for split transactions
2022-03-27 20:06:55 +03:00

100 lines
3.3 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
)
partiallySignedTransaction.Tx.Inputs[i].SigOpCount = byte(len(partiallySignedInput.PubKeySignaturePairs))
}
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
}