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:
Ori Newman 2021-04-06 17:25:09 +03:00 committed by GitHub
parent 7186f83095
commit d2cccd2829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 484 additions and 360 deletions

View File

@ -12,7 +12,7 @@ func main() {
panic(err)
}
privateKey, publicKey, err := libkaspawallet.CreateKeyPair()
privateKey, publicKey, err := libkaspawallet.CreateKeyPair(false)
if err != nil {
panic(err)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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)

View 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
}

View 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
}

View File

@ -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)

View File

@ -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")
}
})
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}