mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-10-14 00:59:33 +00:00
Add ECDSA support to the wallet (#1664)
* Add ECDSA support to the wallet * Fix genkeypair * Fix typo and rename var
This commit is contained in:
parent
7186f83095
commit
d2cccd2829
@ -12,7 +12,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
privateKey, publicKey, err := libkaspawallet.CreateKeyPair()
|
||||
privateKey, publicKey, err := libkaspawallet.CreateKeyPair(false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func balance(conf *balanceConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures)
|
||||
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ type createConfig struct {
|
||||
MinimumSignatures uint32 `long:"min-signatures" short:"m" description:"Minimum required signatures" default:"1"`
|
||||
NumPrivateKeys uint32 `long:"num-private-keys" short:"k" description:"Number of private keys" default:"1"`
|
||||
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)"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ func create(conf *createConfig) error {
|
||||
var publicKeys [][]byte
|
||||
var err error
|
||||
if !conf.Import {
|
||||
encryptedPrivateKeys, publicKeys, err = keys.CreateKeyPairs(conf.NumPrivateKeys)
|
||||
encryptedPrivateKeys, publicKeys, err = keys.CreateKeyPairs(conf.NumPrivateKeys, conf.ECDSA)
|
||||
} else {
|
||||
encryptedPrivateKeys, publicKeys, err = keys.ImportKeyPairs(conf.NumPrivateKeys)
|
||||
}
|
||||
@ -49,7 +49,7 @@ func create(conf *createConfig) error {
|
||||
publicKeys = append(publicKeys, publicKey)
|
||||
}
|
||||
|
||||
err = keys.WriteKeysFile(conf.KeysFile, encryptedPrivateKeys, publicKeys, conf.MinimumSignatures)
|
||||
err = keys.WriteKeysFile(conf.KeysFile, encryptedPrivateKeys, publicKeys, conf.MinimumSignatures, conf.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -59,7 +59,7 @@ func create(conf *createConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures)
|
||||
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures)
|
||||
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -41,13 +41,16 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
psTx, err := libkaspawallet.CreateUnsignedTransaction(keysFile.PublicKeys, keysFile.MinimumSignatures, []*libkaspawallet.Payment{{
|
||||
Address: toAddress,
|
||||
Amount: sendAmountSompi,
|
||||
}, {
|
||||
Address: fromAddress,
|
||||
Amount: changeSompi,
|
||||
}}, selectedUTXOs)
|
||||
psTx, err := libkaspawallet.CreateUnsignedTransaction(keysFile.PublicKeys,
|
||||
keysFile.MinimumSignatures,
|
||||
keysFile.ECDSA,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: toAddress,
|
||||
Amount: sendAmountSompi,
|
||||
}, {
|
||||
Address: fromAddress,
|
||||
Amount: changeSompi,
|
||||
}}, selectedUTXOs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ import (
|
||||
)
|
||||
|
||||
// CreateKeyPairs generates `numKeys` number of key pairs.
|
||||
func CreateKeyPairs(numKeys uint32) (encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
|
||||
func CreateKeyPairs(numKeys uint32, ecdsa bool) (encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
|
||||
return createKeyPairsFromFunction(numKeys, func(_ uint32) ([]byte, []byte, error) {
|
||||
return libkaspawallet.CreateKeyPair()
|
||||
return libkaspawallet.CreateKeyPair(ecdsa)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ type keysFileJSON struct {
|
||||
EncryptedPrivateKeys []*encryptedPrivateKeyJSON `json:"encryptedPrivateKeys"`
|
||||
PublicKeys []string `json:"publicKeys"`
|
||||
MinimumSignatures uint32 `json:"minimumSignatures"`
|
||||
ECDSA bool `json:"ecdsa"`
|
||||
}
|
||||
|
||||
// EncryptedPrivateKey represents an encrypted private key
|
||||
@ -42,6 +43,7 @@ type Data struct {
|
||||
encryptedPrivateKeys []*EncryptedPrivateKey
|
||||
PublicKeys [][]byte
|
||||
MinimumSignatures uint32
|
||||
ECDSA bool
|
||||
}
|
||||
|
||||
func (d *Data) toJSON() *keysFileJSON {
|
||||
@ -62,14 +64,16 @@ func (d *Data) toJSON() *keysFileJSON {
|
||||
EncryptedPrivateKeys: encryptedPrivateKeysJSON,
|
||||
PublicKeys: publicKeysHex,
|
||||
MinimumSignatures: d.MinimumSignatures,
|
||||
ECDSA: d.ECDSA,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Data) fromJSON(kfj *keysFileJSON) error {
|
||||
d.MinimumSignatures = kfj.MinimumSignatures
|
||||
func (d *Data) fromJSON(fileJSON *keysFileJSON) error {
|
||||
d.MinimumSignatures = fileJSON.MinimumSignatures
|
||||
d.ECDSA = fileJSON.ECDSA
|
||||
|
||||
d.encryptedPrivateKeys = make([]*EncryptedPrivateKey, len(kfj.EncryptedPrivateKeys))
|
||||
for i, encryptedPrivateKeyJSON := range kfj.EncryptedPrivateKeys {
|
||||
d.encryptedPrivateKeys = make([]*EncryptedPrivateKey, len(fileJSON.EncryptedPrivateKeys))
|
||||
for i, encryptedPrivateKeyJSON := range fileJSON.EncryptedPrivateKeys {
|
||||
cipher, err := hex.DecodeString(encryptedPrivateKeyJSON.Cipher)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -86,8 +90,8 @@ func (d *Data) fromJSON(kfj *keysFileJSON) error {
|
||||
}
|
||||
}
|
||||
|
||||
d.PublicKeys = make([][]byte, len(kfj.PublicKeys))
|
||||
for i, publicKey := range kfj.PublicKeys {
|
||||
d.PublicKeys = make([][]byte, len(fileJSON.PublicKeys))
|
||||
for i, publicKey := range fileJSON.PublicKeys {
|
||||
var err error
|
||||
d.PublicKeys[i], err = hex.DecodeString(publicKey)
|
||||
if err != nil {
|
||||
@ -172,7 +176,11 @@ func pathExists(path string) (bool, error) {
|
||||
}
|
||||
|
||||
// WriteKeysFile writes a keys file with the given data
|
||||
func WriteKeysFile(path string, encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, minimumSignatures uint32) error {
|
||||
func WriteKeysFile(path string,
|
||||
encryptedPrivateKeys []*EncryptedPrivateKey,
|
||||
publicKeys [][]byte,
|
||||
minimumSignatures uint32,
|
||||
ecdsa bool) error {
|
||||
if path == "" {
|
||||
path = defaultKeysFile
|
||||
}
|
||||
@ -210,6 +218,7 @@ func WriteKeysFile(path string, encryptedPrivateKeys []*EncryptedPrivateKey, pub
|
||||
encryptedPrivateKeys: encryptedPrivateKeys,
|
||||
PublicKeys: publicKeys,
|
||||
MinimumSignatures: minimumSignatures,
|
||||
ECDSA: ecdsa,
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
|
@ -8,7 +8,15 @@ import (
|
||||
)
|
||||
|
||||
// CreateKeyPair generates a private-public key pair
|
||||
func CreateKeyPair() ([]byte, []byte, error) {
|
||||
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")
|
||||
@ -25,11 +33,28 @@ func CreateKeyPair() ([]byte, []byte, error) {
|
||||
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 deserialized private key")
|
||||
return nil, errors.Wrap(err, "Failed to deserialize private key")
|
||||
}
|
||||
|
||||
publicKey, err := keyPair.SchnorrPublicKey()
|
||||
@ -45,40 +70,21 @@ func PublicKeyFromPrivateKey(privateKeyBytes []byte) ([]byte, error) {
|
||||
return publicKeySerialized[:], nil
|
||||
}
|
||||
|
||||
func keyPairBytes(keyPair *secp256k1.SchnorrKeyPair) ([]byte, []byte, error) {
|
||||
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 addressFromPublicKey(params *dagconfig.Params, publicKeySerialized []byte) (util.Address, error) {
|
||||
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to generate p2pk address")
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// Address returns the address associated with the given public keys and minimum signatures parameters.
|
||||
func Address(params *dagconfig.Params, pubKeys [][]byte, minimumSignatures uint32) (util.Address, error) {
|
||||
func Address(params *dagconfig.Params, pubKeys [][]byte, minimumSignatures uint32, ecdsa bool) (util.Address, error) {
|
||||
sortPublicKeys(pubKeys)
|
||||
if uint32(len(pubKeys)) < minimumSignatures {
|
||||
return nil, errors.Errorf("The minimum amount of signatures (%d) is greater than the amount of "+
|
||||
"provided public keys (%d)", minimumSignatures, len(pubKeys))
|
||||
}
|
||||
if len(pubKeys) == 1 {
|
||||
return addressFromPublicKey(params, pubKeys[0])
|
||||
if ecdsa {
|
||||
return util.NewAddressPublicKeyECDSA(pubKeys[0][:], params.Prefix)
|
||||
}
|
||||
return util.NewAddressPublicKey(pubKeys[0][:], params.Prefix)
|
||||
}
|
||||
|
||||
redeemScript, err := multiSigRedeemScript(pubKeys, minimumSignatures)
|
||||
redeemScript, err := multiSigRedeemScript(pubKeys, minimumSignatures, ecdsa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
146
cmd/kaspawallet/libkaspawallet/sign.go
Normal file
146
cmd/kaspawallet/libkaspawallet/sign.go
Normal file
@ -0,0 +1,146 @@
|
||||
package libkaspawallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"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/pkg/errors"
|
||||
)
|
||||
|
||||
type signer interface {
|
||||
rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error)
|
||||
serializedPublicKey() ([]byte, error)
|
||||
}
|
||||
|
||||
type schnorrSigner secp256k1.SchnorrKeyPair
|
||||
|
||||
func (s *schnorrSigner) rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
return txscript.RawTxInSignature(tx, idx, hashType, (*secp256k1.SchnorrKeyPair)(s), sighashReusedValues)
|
||||
}
|
||||
|
||||
func (s *schnorrSigner) serializedPublicKey() ([]byte, error) {
|
||||
publicKey, err := (*secp256k1.SchnorrKeyPair)(s).SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializedPublicKey[:], nil
|
||||
}
|
||||
|
||||
type ecdsaSigner secp256k1.ECDSAPrivateKey
|
||||
|
||||
func (e *ecdsaSigner) rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
return txscript.RawTxInSignatureECDSA(tx, idx, hashType, (*secp256k1.ECDSAPrivateKey)(e), sighashReusedValues)
|
||||
}
|
||||
|
||||
func (e *ecdsaSigner) serializedPublicKey() ([]byte, error) {
|
||||
publicKey, err := (*secp256k1.ECDSAPrivateKey)(e).ECDSAPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializedPublicKey[:], nil
|
||||
}
|
||||
|
||||
func deserializeECDSAPrivateKey(privateKey []byte, ecdsa bool) (signer, error) {
|
||||
if ecdsa {
|
||||
keyPair, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error deserializing private key")
|
||||
}
|
||||
|
||||
return (*ecdsaSigner)(keyPair), nil
|
||||
}
|
||||
|
||||
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error deserializing private key")
|
||||
}
|
||||
|
||||
return (*schnorrSigner)(keyPair), nil
|
||||
}
|
||||
|
||||
// Sign signs the transaction with the given private keys
|
||||
func Sign(privateKeys [][]byte, serializedPSTx []byte, ecdsa bool) ([]byte, error) {
|
||||
keyPairs := make([]signer, len(privateKeys))
|
||||
for i, privateKey := range privateKeys {
|
||||
var err error
|
||||
keyPairs[i], err = deserializeECDSAPrivateKey(privateKey, ecdsa)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error deserializing private key")
|
||||
}
|
||||
}
|
||||
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, keyPair := range keyPairs {
|
||||
err = sign(keyPair, partiallySignedTransaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
|
||||
}
|
||||
|
||||
func sign(keyPair signer, psTx *serialization.PartiallySignedTransaction) error {
|
||||
if isTransactionFullySigned(psTx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
serializedPublicKey, err := keyPair.serializedPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
|
||||
prevOut := partiallySignedInput.PrevOutput
|
||||
psTx.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 psTx.PartiallySignedInputs {
|
||||
for _, pair := range partiallySignedInput.PubKeySignaturePairs {
|
||||
if bytes.Equal(pair.PubKey, serializedPublicKey[:]) {
|
||||
pair.Signature, err = keyPair.rawTxInSignature(psTx.Tx, i, consensushashing.SigHashAll, sighashReusedValues)
|
||||
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
|
||||
}
|
@ -2,14 +2,11 @@ package libkaspawallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"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/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
@ -31,11 +28,12 @@ func sortPublicKeys(publicKeys [][]byte) {
|
||||
func CreateUnsignedTransaction(
|
||||
pubKeys [][]byte,
|
||||
minimumSignatures uint32,
|
||||
ecdsa bool,
|
||||
payments []*Payment,
|
||||
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair) ([]byte, error) {
|
||||
|
||||
sortPublicKeys(pubKeys)
|
||||
unsignedTransaction, err := createUnsignedTransaction(pubKeys, minimumSignatures, payments, selectedUTXOs)
|
||||
unsignedTransaction, err := createUnsignedTransaction(pubKeys, minimumSignatures, ecdsa, payments, selectedUTXOs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -43,53 +41,34 @@ func CreateUnsignedTransaction(
|
||||
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
|
||||
}
|
||||
|
||||
// Sign signs the transaction with the given private keys
|
||||
func Sign(privateKeys [][]byte, serializedPSTx []byte) ([]byte, error) {
|
||||
keyPairs := make([]*secp256k1.SchnorrKeyPair, len(privateKeys))
|
||||
for i, privateKey := range privateKeys {
|
||||
var err error
|
||||
keyPairs[i], err = secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error deserializing private key")
|
||||
}
|
||||
}
|
||||
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, keyPair := range keyPairs {
|
||||
err = sign(keyPair, partiallySignedTransaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
|
||||
}
|
||||
|
||||
func multiSigRedeemScript(pubKeys [][]byte, minimumSignatures uint32) ([]byte, error) {
|
||||
func multiSigRedeemScript(pubKeys [][]byte, minimumSignatures uint32, ecdsa bool) ([]byte, error) {
|
||||
scriptBuilder := txscript.NewScriptBuilder()
|
||||
scriptBuilder.AddInt64(int64(minimumSignatures))
|
||||
for _, key := range pubKeys {
|
||||
scriptBuilder.AddData(key)
|
||||
}
|
||||
scriptBuilder.AddInt64(int64(len(pubKeys)))
|
||||
scriptBuilder.AddOp(txscript.OpCheckMultiSig)
|
||||
|
||||
if ecdsa {
|
||||
scriptBuilder.AddOp(txscript.OpCheckMultiSigECDSA)
|
||||
} else {
|
||||
scriptBuilder.AddOp(txscript.OpCheckMultiSig)
|
||||
}
|
||||
|
||||
return scriptBuilder.Script()
|
||||
}
|
||||
|
||||
func createUnsignedTransaction(
|
||||
pubKeys [][]byte,
|
||||
minimumSignatures uint32,
|
||||
ecdsa bool,
|
||||
payments []*Payment,
|
||||
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair) (*serialization.PartiallySignedTransaction, error) {
|
||||
|
||||
var redeemScript []byte
|
||||
if len(pubKeys) > 1 {
|
||||
var err error
|
||||
redeemScript, err = multiSigRedeemScript(pubKeys, minimumSignatures)
|
||||
redeemScript, err = multiSigRedeemScript(pubKeys, minimumSignatures, ecdsa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -146,53 +125,6 @@ func createUnsignedTransaction(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sign(keyPair *secp256k1.SchnorrKeyPair, psTx *serialization.PartiallySignedTransaction) error {
|
||||
if isTransactionFullySigned(psTx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
publicKey, err := keyPair.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serializedPublicKey, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
|
||||
prevOut := partiallySignedInput.PrevOutput
|
||||
psTx.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 psTx.PartiallySignedInputs {
|
||||
for _, pair := range partiallySignedInput.PubKeySignaturePairs {
|
||||
if bytes.Equal(pair.PubKey, serializedPublicKey[:]) {
|
||||
pair.Signature, err = txscript.RawTxInSignature(psTx.Tx, i, consensushashing.SigHashAll, keyPair, sighashReusedValues)
|
||||
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
|
||||
}
|
||||
|
||||
// IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
|
||||
func IsTransactionFullySigned(psTxBytes []byte) (bool, error) {
|
||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(psTxBytes)
|
||||
|
@ -15,254 +15,277 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
|
||||
t.Run("schnorr", func(t *testing.T) {
|
||||
testFunc(t, false)
|
||||
})
|
||||
|
||||
t.Run("ecdsa", func(t *testing.T) {
|
||||
testFunc(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultisig(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, false, "TestMultisig")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
const numKeys = 3
|
||||
privateKeys := make([][]byte, numKeys)
|
||||
publicKeys := make([][]byte, numKeys)
|
||||
for i := 0; i < numKeys; i++ {
|
||||
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair()
|
||||
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, false, "TestMultisig")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateKeyPair: %+v", err)
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
const minimumSignatures = 2
|
||||
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures)
|
||||
if err != nil {
|
||||
t.Fatalf("Address: %+v", err)
|
||||
}
|
||||
const numKeys = 3
|
||||
privateKeys := make([][]byte, numKeys)
|
||||
publicKeys := make([][]byte, numKeys)
|
||||
for i := 0; i < numKeys; i++ {
|
||||
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair(ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateKeyPair: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := address.(*util.AddressScriptHash); !ok {
|
||||
t.Fatalf("The address is of unexpected type")
|
||||
}
|
||||
const minimumSignatures = 2
|
||||
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Address: %+v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: %+v", err)
|
||||
}
|
||||
if _, ok := address.(*util.AddressScriptHash); !ok {
|
||||
t.Fatalf("The address is of unexpected type")
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
ExtraData: nil,
|
||||
}
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: %+v", err)
|
||||
}
|
||||
|
||||
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
ExtraData: nil,
|
||||
}
|
||||
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block1, err := tc.GetBlock(block1Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block1Tx := block1.Transactions[0]
|
||||
block1TxOut := block1Tx.Outputs[0]
|
||||
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
block1, err := tc.GetBlock(block1Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
|
||||
block1Tx := block1.Transactions[0]
|
||||
block1TxOut := block1Tx.Outputs[0]
|
||||
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
}}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, ecdsa,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: address,
|
||||
Amount: 10,
|
||||
}}, selectedUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateUnsignedTransaction: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
if isFullySigned {
|
||||
t.Fatalf("Transaction is not expected to be signed")
|
||||
}
|
||||
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
|
||||
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 := libkaspawallet.Sign(privateKeys[:1], unsignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err = libkaspawallet.IsTransactionFullySigned(signedTxStep1)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
if isFullySigned {
|
||||
t.Fatalf("Transaction is not expected to be fully signed")
|
||||
}
|
||||
|
||||
signedTxStep2, err := libkaspawallet.Sign(privateKeys[1:2], signedTxStep1, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
extractedSignedTxStep2, err := libkaspawallet.ExtractTransaction(signedTxStep2)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
|
||||
signedTxOneStep, err := libkaspawallet.Sign(privateKeys[:2], unsignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
extractedSignedTxOneStep, err := libkaspawallet.ExtractTransaction(signedTxOneStep)
|
||||
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")
|
||||
}
|
||||
|
||||
_, insertionResult, 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,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
}}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, []*libkaspawallet.Payment{{
|
||||
Address: address,
|
||||
Amount: 10,
|
||||
}}, selectedUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateUnsignedTransaction: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
if isFullySigned {
|
||||
t.Fatalf("Transaction is not expected to be signed")
|
||||
}
|
||||
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
|
||||
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 := libkaspawallet.Sign(privateKeys[:1], unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err = libkaspawallet.IsTransactionFullySigned(signedTxStep1)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
if isFullySigned {
|
||||
t.Fatalf("Transaction is not expected to be fully signed")
|
||||
}
|
||||
|
||||
signedTxStep2, err := libkaspawallet.Sign(privateKeys[1:2], signedTxStep1)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
extractedSignedTxStep2, err := libkaspawallet.ExtractTransaction(signedTxStep2)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
|
||||
signedTxOneStep, err := libkaspawallet.Sign(privateKeys[:2], unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %+v", err)
|
||||
}
|
||||
|
||||
extractedSignedTxOneStep, err := libkaspawallet.ExtractTransaction(signedTxOneStep)
|
||||
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")
|
||||
}
|
||||
|
||||
_, insertionResult, 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 !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
|
||||
t.Fatalf("Transaction wasn't accepted in the DAG")
|
||||
}
|
||||
}
|
||||
if !insertionResult.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, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, false, "TestMultisig")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
const numKeys = 1
|
||||
privateKeys := make([][]byte, numKeys)
|
||||
publicKeys := make([][]byte, numKeys)
|
||||
for i := 0; i < numKeys; i++ {
|
||||
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair()
|
||||
forSchnorrAndECDSA(t, func(t *testing.T, ecdsa bool) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, false, "TestMultisig")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateKeyPair: %+v", err)
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
const minimumSignatures = 1
|
||||
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures)
|
||||
if err != nil {
|
||||
t.Fatalf("Address: %+v", err)
|
||||
}
|
||||
const numKeys = 1
|
||||
privateKeys := make([][]byte, numKeys)
|
||||
publicKeys := make([][]byte, numKeys)
|
||||
for i := 0; i < numKeys; i++ {
|
||||
privateKeys[i], publicKeys[i], err = libkaspawallet.CreateKeyPair(ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateKeyPair: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := address.(*util.AddressPublicKey); !ok {
|
||||
t.Fatalf("The address is of unexpected type")
|
||||
}
|
||||
const minimumSignatures = 1
|
||||
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("Address: %+v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: %+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")
|
||||
}
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
ExtraData: nil,
|
||||
}
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: %+v", err)
|
||||
}
|
||||
|
||||
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
ExtraData: nil,
|
||||
}
|
||||
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block1, err := tc.GetBlock(block1Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block1Tx := block1.Transactions[0]
|
||||
block1TxOut := block1Tx.Outputs[0]
|
||||
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
block1, err := tc.GetBlock(block1Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
|
||||
block1Tx := block1.Transactions[0]
|
||||
block1TxOut := block1Tx.Outputs[0]
|
||||
selectedUTXOs := []*externalapi.OutpointAndUTXOEntryPair{{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
}}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
||||
ecdsa,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: address,
|
||||
Amount: 10,
|
||||
}}, selectedUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateUnsignedTransaction: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
if isFullySigned {
|
||||
t.Fatalf("Transaction is not expected to be signed")
|
||||
}
|
||||
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
|
||||
if err == nil || !strings.Contains(err.Error(), "missing signature") {
|
||||
t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction")
|
||||
}
|
||||
|
||||
signedTx, err := libkaspawallet.Sign(privateKeys, unsignedTransaction, ecdsa)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
tx, err := libkaspawallet.ExtractTransaction(signedTx)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
|
||||
_, insertionResult, 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,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0),
|
||||
}}
|
||||
|
||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, []*libkaspawallet.Payment{{
|
||||
Address: address,
|
||||
Amount: 10,
|
||||
}}, selectedUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateUnsignedTransaction: %+v", err)
|
||||
}
|
||||
|
||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
if isFullySigned {
|
||||
t.Fatalf("Transaction is not expected to be signed")
|
||||
}
|
||||
|
||||
_, err = libkaspawallet.ExtractTransaction(unsignedTransaction)
|
||||
if err == nil || !strings.Contains(err.Error(), "missing signature") {
|
||||
t.Fatal("Unexpectedly succeed to extract a valid transaction out of unsigned transaction")
|
||||
}
|
||||
|
||||
signedTx, err := libkaspawallet.Sign(privateKeys, unsignedTransaction)
|
||||
if err != nil {
|
||||
t.Fatalf("IsTransactionFullySigned: %+v", err)
|
||||
}
|
||||
|
||||
tx, err := libkaspawallet.ExtractTransaction(signedTx)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractTransaction: %+v", err)
|
||||
}
|
||||
|
||||
_, insertionResult, 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 !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
|
||||
t.Fatalf("Transaction wasn't accepted in the DAG")
|
||||
}
|
||||
}
|
||||
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
|
||||
t.Fatalf("Transaction wasn't accepted in the DAG")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func send(conf *sendConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures)
|
||||
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -49,13 +49,17 @@ func send(conf *sendConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
psTx, err := libkaspawallet.CreateUnsignedTransaction(keysFile.PublicKeys, keysFile.MinimumSignatures, []*libkaspawallet.Payment{{
|
||||
Address: toAddress,
|
||||
Amount: sendAmountSompi,
|
||||
}, {
|
||||
Address: fromAddress,
|
||||
Amount: changeSompi,
|
||||
}}, selectedUTXOs)
|
||||
psTx, err := libkaspawallet.CreateUnsignedTransaction(keysFile.PublicKeys,
|
||||
keysFile.MinimumSignatures,
|
||||
keysFile.ECDSA,
|
||||
[]*libkaspawallet.Payment{{
|
||||
Address: toAddress,
|
||||
Amount: sendAmountSompi,
|
||||
}, {
|
||||
Address: fromAddress,
|
||||
Amount: changeSompi,
|
||||
}},
|
||||
selectedUTXOs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -65,7 +69,7 @@ func send(conf *sendConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedPSTx, err := libkaspawallet.Sign(privateKeys, psTx)
|
||||
updatedPSTx, err := libkaspawallet.Sign(privateKeys, psTx, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ func showAddress(conf *showAddressConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
address, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures)
|
||||
address, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ func sign(conf *signConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedPSTxBytes, err := libkaspawallet.Sign(privateKeys, psTxBytes)
|
||||
updatedPSTxBytes, err := libkaspawallet.Sign(privateKeys, psTxBytes, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user