mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-22 03:32:55 +00:00
Compare commits
15 Commits
v0.10.0-al
...
ghostdagRe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a83f9bac | ||
|
|
7d20ee6b58 | ||
|
|
1ab05c3fbc | ||
|
|
347dd8fc4b | ||
|
|
d2cccd2829 | ||
|
|
7186f83095 | ||
|
|
5c394c2951 | ||
|
|
a786cdc15e | ||
|
|
6dd3d4a9e7 | ||
|
|
73b36f12f0 | ||
|
|
a795a9e619 | ||
|
|
0be1bba408 | ||
|
|
6afc06ce58 | ||
|
|
d01a213f3d | ||
|
|
7ad8ce521c |
9
cmd/genkeypair/README.md
Normal file
9
cmd/genkeypair/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
genkeypair
|
||||
========
|
||||
|
||||
A tool for generating private-key-address pairs.
|
||||
|
||||
Note: This tool prints unencrypted private keys and is not recommended for day
|
||||
to day use, and is intended mainly for tests.
|
||||
|
||||
In order to manage your funds it's recommended to use [kaspawallet](../kaspawallet)
|
||||
26
cmd/genkeypair/config.go
Normal file
26
cmd/genkeypair/config.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
)
|
||||
|
||||
type configFlags struct {
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
func parseConfig() (*configFlags, error) {
|
||||
cfg := &configFlags{}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cfg.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
27
cmd/genkeypair/main.go
Normal file
27
cmd/genkeypair/main.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := parseConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
privateKey, publicKey, err := libkaspawallet.CreateKeyPair(false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addr, err := util.NewAddressPublicKey(publicKey, cfg.NetParams().Prefix)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Private key: %x\n", privateKey)
|
||||
fmt.Printf("Address: %s\n", addr)
|
||||
}
|
||||
@@ -5,72 +5,32 @@ import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const minerTimeout = 10 * time.Second
|
||||
|
||||
type minerClient struct {
|
||||
isReconnecting uint32
|
||||
clientLock sync.RWMutex
|
||||
rpcClient *rpcclient.RPCClient
|
||||
*rpcclient.RPCClient
|
||||
|
||||
cfg *configFlags
|
||||
blockAddedNotificationChan chan struct{}
|
||||
}
|
||||
|
||||
func (mc *minerClient) safeRPCClient() *rpcclient.RPCClient {
|
||||
mc.clientLock.RLock()
|
||||
defer mc.clientLock.RUnlock()
|
||||
return mc.rpcClient
|
||||
}
|
||||
|
||||
func (mc *minerClient) reconnect() {
|
||||
swapped := atomic.CompareAndSwapUint32(&mc.isReconnecting, 0, 1)
|
||||
if !swapped {
|
||||
return
|
||||
}
|
||||
|
||||
defer atomic.StoreUint32(&mc.isReconnecting, 0)
|
||||
|
||||
mc.clientLock.Lock()
|
||||
defer mc.clientLock.Unlock()
|
||||
|
||||
retryDuration := time.Second
|
||||
const maxRetryDuration = time.Minute
|
||||
log.Infof("Reconnecting RPC connection")
|
||||
for {
|
||||
err := mc.connect()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if retryDuration < time.Minute {
|
||||
retryDuration *= 2
|
||||
} else {
|
||||
retryDuration = maxRetryDuration
|
||||
}
|
||||
|
||||
log.Errorf("Got error '%s' while reconnecting. Trying again in %s", err, retryDuration)
|
||||
time.Sleep(retryDuration)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *minerClient) connect() error {
|
||||
rpcAddress, err := mc.cfg.NetParams().NormalizeRPCServerAddress(mc.cfg.RPCServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mc.rpcClient, err = rpcclient.NewRPCClient(rpcAddress)
|
||||
rpcClient, err := rpcclient.NewRPCClient(rpcAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mc.rpcClient.SetTimeout(minerTimeout)
|
||||
mc.rpcClient.SetLogger(backendLog, logger.LevelTrace)
|
||||
mc.RPCClient = rpcClient
|
||||
mc.SetTimeout(minerTimeout)
|
||||
mc.SetLogger(backendLog, logger.LevelTrace)
|
||||
|
||||
err = mc.rpcClient.RegisterForBlockAddedNotifications(func(_ *appmessage.BlockAddedNotificationMessage) {
|
||||
err = mc.RegisterForBlockAddedNotifications(func(_ *appmessage.BlockAddedNotificationMessage) {
|
||||
select {
|
||||
case mc.blockAddedNotificationChan <- struct{}{}:
|
||||
default:
|
||||
|
||||
@@ -40,7 +40,7 @@ func main() {
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error connecting to the RPC server"))
|
||||
}
|
||||
defer client.safeRPCClient().Disconnect()
|
||||
defer client.Disconnect()
|
||||
|
||||
miningAddr, err := util.DecodeAddress(cfg.MiningAddr, cfg.ActiveNetParams.Prefix)
|
||||
if err != nil {
|
||||
|
||||
@@ -114,13 +114,17 @@ func logHashRate() {
|
||||
|
||||
func handleFoundBlock(client *minerClient, block *externalapi.DomainBlock) error {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
log.Infof("Submitting block %s to %s", blockHash, client.safeRPCClient().Address())
|
||||
log.Infof("Submitting block %s to %s", blockHash, client.Address())
|
||||
|
||||
rejectReason, err := client.safeRPCClient().SubmitBlock(block)
|
||||
rejectReason, err := client.SubmitBlock(block)
|
||||
if err != nil {
|
||||
if nativeerrors.Is(err, router.ErrTimeout) {
|
||||
log.Warnf("Got timeout while submitting block %s to %s: %s", blockHash, client.safeRPCClient().Address(), err)
|
||||
client.reconnect()
|
||||
log.Warnf("Got timeout while submitting block %s to %s: %s", blockHash, client.Address(), err)
|
||||
return client.Reconnect()
|
||||
}
|
||||
if nativeerrors.Is(err, router.ErrRouteClosed) {
|
||||
log.Debugf("Got route is closed while requesting block template from %s. "+
|
||||
"The client is most likely reconnecting", client.Address())
|
||||
return nil
|
||||
}
|
||||
if rejectReason == appmessage.RejectReasonIsInIBD {
|
||||
@@ -129,7 +133,7 @@ func handleFoundBlock(client *minerClient, block *externalapi.DomainBlock) error
|
||||
time.Sleep(waitTime)
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "Error submitting block %s to %s", blockHash, client.safeRPCClient().Address())
|
||||
return errors.Wrapf(err, "Error submitting block %s to %s", blockHash, client.Address())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -188,19 +192,27 @@ func getBlockForMining(mineWhenNotSynced bool) *externalapi.DomainBlock {
|
||||
|
||||
func templatesLoop(client *minerClient, miningAddr util.Address, errChan chan error) {
|
||||
getBlockTemplate := func() {
|
||||
template, err := client.safeRPCClient().GetBlockTemplate(miningAddr.String())
|
||||
template, err := client.GetBlockTemplate(miningAddr.String())
|
||||
if nativeerrors.Is(err, router.ErrTimeout) {
|
||||
log.Warnf("Got timeout while requesting block template from %s: %s", client.safeRPCClient().Address(), err)
|
||||
client.reconnect()
|
||||
log.Warnf("Got timeout while requesting block template from %s: %s", client.Address(), err)
|
||||
reconnectErr := client.Reconnect()
|
||||
if reconnectErr != nil {
|
||||
errChan <- reconnectErr
|
||||
}
|
||||
return
|
||||
}
|
||||
if nativeerrors.Is(err, router.ErrRouteClosed) {
|
||||
log.Debugf("Got route is closed while requesting block template from %s. "+
|
||||
"The client is most likely reconnecting", client.Address())
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- errors.Wrapf(err, "Error getting block template from %s", client.safeRPCClient().Address())
|
||||
errChan <- errors.Wrapf(err, "Error getting block template from %s", client.Address())
|
||||
return
|
||||
}
|
||||
err = templatemanager.Set(template)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrapf(err, "Error setting block template from %s", client.safeRPCClient().Address())
|
||||
errChan <- errors.Wrapf(err, "Error setting block template from %s", client.Address())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ const (
|
||||
createSubCmd = "create"
|
||||
balanceSubCmd = "balance"
|
||||
sendSubCmd = "send"
|
||||
createUnsignedTransactionSubCmd = "createUnsignedTransaction"
|
||||
createUnsignedTransactionSubCmd = "create-unsigned-transaction"
|
||||
signSubCmd = "sign"
|
||||
broadcastSubCmd = "broadcast"
|
||||
showAddressSubCmd = "show-address"
|
||||
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
|
||||
)
|
||||
|
||||
type configFlags struct {
|
||||
@@ -22,21 +24,23 @@ type configFlags struct {
|
||||
}
|
||||
|
||||
type createConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (only if different from default)"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
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 to generate" 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
|
||||
}
|
||||
|
||||
type balanceConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (only if different from default)"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
type sendConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (only if different from default)"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
|
||||
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
|
||||
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
|
||||
@@ -44,7 +48,7 @@ type sendConfig struct {
|
||||
}
|
||||
|
||||
type createUnsignedTransactionConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (only if different from default)"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
|
||||
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
|
||||
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"`
|
||||
@@ -52,7 +56,7 @@ type createUnsignedTransactionConfig struct {
|
||||
}
|
||||
|
||||
type signConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (only if different from default)"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
Transaction string `long:"transaction" short:"t" description:"The unsigned transaction to sign on (encoded in hex)" required:"true"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
@@ -63,6 +67,16 @@ type broadcastConfig struct {
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
type showAddressConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
type dumpUnencryptedDataConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
func parseCommandLine() (subCommand string, config interface{}) {
|
||||
cfg := &configFlags{}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
@@ -91,6 +105,15 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
parser.AddCommand(broadcastSubCmd, "Broadcast the given transaction",
|
||||
"Broadcast the given transaction", broadcastConf)
|
||||
|
||||
showAddressConf := &showAddressConfig{}
|
||||
parser.AddCommand(showAddressSubCmd, "Shows the public address of the current wallet",
|
||||
"Shows the public address of the current wallet", showAddressConf)
|
||||
|
||||
dumpUnencryptedDataConf := &dumpUnencryptedDataConfig{}
|
||||
parser.AddCommand(dumpUnencryptedDataSubCmd, "Prints the unencrypted wallet data",
|
||||
"Prints the unencrypted wallet data including its private keys. Anyone that sees it can access "+
|
||||
"the funds. Use only on safe environment.", dumpUnencryptedDataConf)
|
||||
|
||||
_, err := parser.Parse()
|
||||
|
||||
if err != nil {
|
||||
@@ -146,6 +169,20 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
printErrorAndExit(err)
|
||||
}
|
||||
config = broadcastConf
|
||||
case showAddressSubCmd:
|
||||
combineNetworkFlags(&showAddressConf.NetworkFlags, &cfg.NetworkFlags)
|
||||
err := showAddressConf.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
printErrorAndExit(err)
|
||||
}
|
||||
config = showAddressConf
|
||||
case dumpUnencryptedDataSubCmd:
|
||||
combineNetworkFlags(&dumpUnencryptedDataConf.NetworkFlags, &cfg.NetworkFlags)
|
||||
err := dumpUnencryptedDataConf.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
printErrorAndExit(err)
|
||||
}
|
||||
config = dumpUnencryptedDataConf
|
||||
}
|
||||
|
||||
return parser.Command.Active.Name, config
|
||||
|
||||
@@ -11,7 +11,14 @@ import (
|
||||
)
|
||||
|
||||
func create(conf *createConfig) error {
|
||||
encryptedPrivateKeys, publicKeys, err := keys.CreateKeyPairs(conf.NumPrivateKeys)
|
||||
var encryptedPrivateKeys []*keys.EncryptedPrivateKey
|
||||
var publicKeys [][]byte
|
||||
var err error
|
||||
if !conf.Import {
|
||||
encryptedPrivateKeys, publicKeys, err = keys.CreateKeyPairs(conf.NumPrivateKeys, conf.ECDSA)
|
||||
} else {
|
||||
encryptedPrivateKeys, publicKeys, err = keys.ImportKeyPairs(conf.NumPrivateKeys)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,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
|
||||
}
|
||||
@@ -52,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
|
||||
}
|
||||
|
||||
69
cmd/kaspawallet/dump_unencrypted_data.go
Normal file
69
cmd/kaspawallet/dump_unencrypted_data.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error {
|
||||
err := confirmDump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeys, err := keysFile.DecryptPrivateKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeysPublicKeys := make(map[string]struct{})
|
||||
for i, privateKey := range privateKeys {
|
||||
fmt.Printf("Private key #%d:\n%x\n\n", i+1, privateKey)
|
||||
publicKey, err := libkaspawallet.PublicKeyFromPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeysPublicKeys[string(publicKey)] = struct{}{}
|
||||
}
|
||||
|
||||
i := 1
|
||||
for _, publicKey := range keysFile.PublicKeys {
|
||||
if _, exists := privateKeysPublicKeys[string(publicKey)]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Public key #%d:\n%x\n\n", i, publicKey)
|
||||
i++
|
||||
}
|
||||
|
||||
fmt.Printf("Minimum number of signatures: %d\n", keysFile.MinimumSignatures)
|
||||
return nil
|
||||
}
|
||||
|
||||
func confirmDump() error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("This operation will print your unencrypted keys on the screen. Anyone that sees this information " +
|
||||
"will be able to steal your funds. Are you sure you want to proceed (y/N)? ")
|
||||
line, isPrefix, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
if isPrefix || string(line) != "y" {
|
||||
return errors.Errorf("Dump aborted by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,14 +1,52 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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(ecdsa)
|
||||
})
|
||||
}
|
||||
|
||||
// ImportKeyPairs imports a `numKeys` of private keys and generates key pairs out of them.
|
||||
func ImportKeyPairs(numKeys uint32) (encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
|
||||
return createKeyPairsFromFunction(numKeys, func(keyIndex uint32) ([]byte, []byte, error) {
|
||||
fmt.Printf("Enter private key #%d here:\n", keyIndex+1)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
line, isPrefix, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if isPrefix {
|
||||
return nil, nil, errors.Errorf("Private key is too long")
|
||||
}
|
||||
privateKey, err := hex.DecodeString(string(line))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
publicKey, err := libkaspawallet.PublicKeyFromPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return privateKey, publicKey, nil
|
||||
})
|
||||
}
|
||||
|
||||
func createKeyPairsFromFunction(numKeys uint32, keyPairFunction func(keyIndex uint32) ([]byte, []byte, error)) (
|
||||
encryptedPrivateKeys []*EncryptedPrivateKey, publicKeys [][]byte, err error) {
|
||||
|
||||
password := getPassword("Enter password for the key file:")
|
||||
confirmPassword := getPassword("Confirm password:")
|
||||
|
||||
@@ -18,7 +56,7 @@ func CreateKeyPairs(numKeys uint32) (encryptedPrivateKeys []*EncryptedPrivateKey
|
||||
|
||||
encryptedPrivateKeys = make([]*EncryptedPrivateKey, 0, numKeys)
|
||||
for i := uint32(0); i < numKeys; i++ {
|
||||
privateKey, publicKey, err := libkaspawallet.CreateKeyPair()
|
||||
privateKey, publicKey, err := keyPairFunction(i)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -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,12 +8,20 @@ import (
|
||||
)
|
||||
|
||||
// CreateKeyPair generates a private-public key pair
|
||||
func CreateKeyPair() ([]byte, []byte, error) {
|
||||
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
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 := privateKey.SchnorrPublicKey()
|
||||
publicKey, err := keyPair.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Failed to generate public key")
|
||||
}
|
||||
@@ -22,30 +30,61 @@ func CreateKeyPair() ([]byte, []byte, error) {
|
||||
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
|
||||
}
|
||||
|
||||
return privateKey.SerializePrivateKey()[:], publicKeySerialized[:], nil
|
||||
return keyPair.SerializePrivateKey()[:], publicKeySerialized[:], nil
|
||||
}
|
||||
|
||||
func addressFromPublicKey(params *dagconfig.Params, publicKeySerialized []byte) (util.Address, error) {
|
||||
addr, err := util.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
func createKeyPairECDSA() ([]byte, []byte, error) {
|
||||
keyPair, err := secp256k1.GenerateECDSAPrivateKey()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to generate p2pkh address")
|
||||
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 addr, nil
|
||||
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, 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)
|
||||
@@ -254,7 +186,7 @@ func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*extern
|
||||
psTx.Tx.Inputs[i].SignatureScript = sigScript
|
||||
} else {
|
||||
if len(input.PubKeySignaturePairs) > 1 {
|
||||
return nil, errors.Errorf("Cannot sign on P2PKH when len(input.PubKeySignaturePairs) > 1")
|
||||
return nil, errors.Errorf("Cannot sign on P2PK when len(input.PubKeySignaturePairs) > 1")
|
||||
}
|
||||
|
||||
if input.PubKeySignaturePairs[0].Signature == nil {
|
||||
@@ -263,7 +195,6 @@ func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*extern
|
||||
|
||||
sigScript, err := txscript.NewScriptBuilder().
|
||||
AddData(input.PubKeySignaturePairs[0].Signature).
|
||||
AddData(input.PubKeySignaturePairs[0].PubKey).
|
||||
Script()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -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 TestP2PKH(t *testing.T) {
|
||||
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.AddressPubKeyHash); !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")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func main() {
|
||||
err = sign(config.(*signConfig))
|
||||
case broadcastSubCmd:
|
||||
err = broadcast(config.(*broadcastConfig))
|
||||
case showAddressSubCmd:
|
||||
err = showAddress(config.(*showAddressConfig))
|
||||
case dumpUnencryptedDataSubCmd:
|
||||
err = dumpUnencryptedData(config.(*dumpUnencryptedDataConfig))
|
||||
default:
|
||||
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
22
cmd/kaspawallet/show_address.go
Normal file
22
cmd/kaspawallet/show_address.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
)
|
||||
|
||||
func showAddress(conf *showAddressConfig) error {
|
||||
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
address, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("The wallet address is:\n%s\n", address)
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ type TestConsensusStateManager interface {
|
||||
model.ConsensusStateManager
|
||||
AddUTXOToMultiset(multiset model.Multiset, entry externalapi.UTXOEntry,
|
||||
outpoint *externalapi.DomainOutpoint) error
|
||||
ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error)
|
||||
ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error)
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ func (bb *testBlockBuilder) buildBlockWithParents(stagingArea *model.StagingArea
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
selectedParentStatus, err := bb.testConsensus.ConsensusStateManager().ResolveBlockStatus(stagingArea, ghostdagData.SelectedParent())
|
||||
selectedParentStatus, err := bb.testConsensus.ConsensusStateManager().ResolveBlockStatus(
|
||||
stagingArea, ghostdagData.SelectedParent(), false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block
|
||||
|
||||
if !isViolatingFinality {
|
||||
log.Debugf("Block %s doesn't violate finality. Resolving its block status", blockHash)
|
||||
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash)
|
||||
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "RestorePastUTXOSetIterator")
|
||||
defer onEnd()
|
||||
|
||||
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash)
|
||||
blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (csm *consensusStateManager) resolveBlockStatus(
|
||||
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error) {
|
||||
func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, fmt.Sprintf("resolveBlockStatus for %s", blockHash))
|
||||
defer onEnd()
|
||||
@@ -53,8 +53,8 @@ func (csm *consensusStateManager) resolveBlockStatus(
|
||||
unverifiedBlockHash := unverifiedBlocks[i]
|
||||
|
||||
stagingAreaForCurrentBlock := stagingArea
|
||||
isCurrentBlockTopBlock := i == 0
|
||||
if !isCurrentBlockTopBlock {
|
||||
useSeparateStagingArea := useSeparateStagingAreasPerBlock && (i != 0)
|
||||
if useSeparateStagingArea {
|
||||
stagingAreaForCurrentBlock = model.NewStagingArea()
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (csm *consensusStateManager) resolveBlockStatus(
|
||||
log.Debugf("Block %s status resolved to `%s`, finished %d/%d of unverified blocks",
|
||||
unverifiedBlockHash, blockStatus, len(unverifiedBlocks)-i, len(unverifiedBlocks))
|
||||
|
||||
if !isCurrentBlockTopBlock {
|
||||
if useSeparateStagingArea {
|
||||
err := staging.CommitAllChanges(csm.databaseContext, stagingAreaForCurrentBlock)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
||||
@@ -147,7 +147,8 @@ func TestDoubleSpends(t *testing.T) {
|
||||
t.Fatalf("Error adding goodBlock: %+v", err)
|
||||
}
|
||||
//use ResolveBlockStatus, since goodBlock2 might not be the selectedTip
|
||||
goodBlock2Status, err := consensus.ConsensusStateManager().ResolveBlockStatus(stagingArea, goodBlock2Hash)
|
||||
goodBlock2Status, err := consensus.ConsensusStateManager().ResolveBlockStatus(
|
||||
stagingArea, goodBlock2Hash, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting status of goodBlock: %+v", err)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ func (csm *testConsensusStateManager) AddUTXOToMultiset(
|
||||
return addUTXOToMultiset(multiset, entry, outpoint)
|
||||
}
|
||||
|
||||
func (csm *testConsensusStateManager) ResolveBlockStatus(stagingArea *model.StagingArea,
|
||||
blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error) {
|
||||
func (csm *testConsensusStateManager) ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) {
|
||||
|
||||
return csm.resolveBlockStatus(stagingArea, blockHash)
|
||||
return csm.resolveBlockStatus(stagingArea, blockHash, useSeparateStagingAreasPerBlock)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package ghostdag2
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"sort"
|
||||
|
||||
"math/big"
|
||||
|
||||
@@ -19,7 +18,7 @@ type ghostdagHelper struct {
|
||||
headerStore model.BlockHeaderStore
|
||||
}
|
||||
|
||||
// New creates a new instance of this alternative ghostdag impl
|
||||
// New creates a new instance of this alternative GHOSTDAG impl
|
||||
func New(
|
||||
databaseContext model.DBReader,
|
||||
dagTopologyManager model.DAGTopologyManager,
|
||||
@@ -36,53 +35,60 @@ func New(
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------- */
|
||||
|
||||
func (gh *ghostdagHelper) GHOSTDAG(stagingArea *model.StagingArea, blockCandidate *externalapi.DomainHash) error {
|
||||
myWork := new(big.Int)
|
||||
maxWork := new(big.Int)
|
||||
var myScore uint64
|
||||
var spScore uint64
|
||||
/* find the selectedParent */
|
||||
blockParents, err := gh.dagTopologyManager.Parents(stagingArea, blockCandidate)
|
||||
var blueScore uint64 = 0
|
||||
var blueWork = new(big.Int)
|
||||
blueWork.SetUint64(0)
|
||||
var selectedParent *externalapi.DomainHash = nil
|
||||
var mergeSetBlues = make([]*externalapi.DomainHash, 0)
|
||||
var mergeSetReds = make([]*externalapi.DomainHash, 0)
|
||||
|
||||
parents, err := gh.dagTopologyManager.Parents(stagingArea, blockCandidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var selectedParent = blockParents[0]
|
||||
for _, parent := range blockParents {
|
||||
blockData, err := gh.dataStore.Get(gh.dbAccess, stagingArea, parent)
|
||||
// If genesis:
|
||||
if len(parents) == 0 {
|
||||
blockGHOSTDAGData := model.NewBlockGHOSTDAGData(blueScore, blueWork, selectedParent, mergeSetBlues, mergeSetReds, nil)
|
||||
gh.dataStore.Stage(stagingArea, blockCandidate, blockGHOSTDAGData)
|
||||
return nil
|
||||
}
|
||||
|
||||
maxBlueWork := new(big.Int)
|
||||
maxBlueWork.SetUint64(0)
|
||||
maxBlueScore := uint64(0)
|
||||
selectedParent = parents[0]
|
||||
// Find the selected parent.
|
||||
for _, parent := range parents {
|
||||
parentBlockData, err := gh.dataStore.Get(gh.dbAccess, stagingArea, parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockWork := blockData.BlueWork()
|
||||
blockScore := blockData.BlueScore()
|
||||
if blockWork.Cmp(maxWork) == 1 {
|
||||
parentBlueWork := parentBlockData.BlueWork()
|
||||
switch parentBlueWork.Cmp(maxBlueWork) {
|
||||
case 0:
|
||||
if isMoreHash(parent, selectedParent) {
|
||||
selectedParent = parent
|
||||
maxBlueWork = parentBlueWork
|
||||
maxBlueScore = parentBlockData.BlueScore()
|
||||
}
|
||||
case 1:
|
||||
selectedParent = parent
|
||||
maxWork = blockWork
|
||||
spScore = blockScore
|
||||
}
|
||||
if blockWork.Cmp(maxWork) == 0 && ismoreHash(parent, selectedParent) {
|
||||
selectedParent = parent
|
||||
maxWork = blockWork
|
||||
spScore = blockScore
|
||||
maxBlueWork = parentBlueWork
|
||||
maxBlueScore = parentBlockData.BlueScore()
|
||||
}
|
||||
}
|
||||
myWork.Set(maxWork)
|
||||
myScore = spScore
|
||||
|
||||
/* Goal: iterate blockCandidate's mergeSet and divide it to : blue, blues, reds. */
|
||||
var mergeSetBlues = make([]*externalapi.DomainHash, 0)
|
||||
var mergeSetReds = make([]*externalapi.DomainHash, 0)
|
||||
var blueSet = make([]*externalapi.DomainHash, 0)
|
||||
blueWork.Set(maxBlueWork)
|
||||
blueScore = maxBlueScore
|
||||
|
||||
blueSet := make([]*externalapi.DomainHash, 0)
|
||||
blueSet = append(blueSet, selectedParent)
|
||||
mergeSetBlues = append(mergeSetBlues, selectedParent)
|
||||
|
||||
mergeSetArr, err := gh.findMergeSet(stagingArea, blockParents, selectedParent)
|
||||
mergeSet, err := gh.findMergeSet(stagingArea, parents, selectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gh.sortByBlueWork(stagingArea, mergeSetArr)
|
||||
err = gh.sortByBlueWork(stagingArea, mergeSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,41 +96,36 @@ func (gh *ghostdagHelper) GHOSTDAG(stagingArea *model.StagingArea, blockCandidat
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mergeSetBlock := range mergeSetArr {
|
||||
// Iterate on mergeSet and divide it to mergeSetBlues and mergeSetReds.
|
||||
for _, mergeSetBlock := range mergeSet {
|
||||
if mergeSetBlock.Equal(selectedParent) {
|
||||
if !contains(selectedParent, mergeSetBlues) {
|
||||
mergeSetBlues = append(mergeSetBlues, selectedParent)
|
||||
blueSet = append(blueSet, selectedParent)
|
||||
}
|
||||
continue
|
||||
}
|
||||
err := gh.divideBlueRed(stagingArea, selectedParent, mergeSetBlock, &mergeSetBlues, &mergeSetReds, &blueSet)
|
||||
err := gh.divideToBlueAndRed(stagingArea, mergeSetBlock, &mergeSetBlues, &mergeSetReds, &blueSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
myScore += uint64(len(mergeSetBlues))
|
||||
blueScore += uint64(len(mergeSetBlues))
|
||||
|
||||
// We add up all the *work*(not blueWork) that all our blues and selected parent did
|
||||
// Calculation of blue work
|
||||
for _, blue := range mergeSetBlues {
|
||||
header, err := gh.headerStore.BlockHeader(gh.dbAccess, stagingArea, blue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
myWork.Add(myWork, difficulty.CalcWork(header.Bits()))
|
||||
blueWork.Add(blueWork, difficulty.CalcWork(header.Bits()))
|
||||
}
|
||||
|
||||
e := model.NewBlockGHOSTDAGData(myScore, myWork, selectedParent, mergeSetBlues, mergeSetReds, nil)
|
||||
gh.dataStore.Stage(stagingArea, blockCandidate, e)
|
||||
blockGHOSTDAGData := model.NewBlockGHOSTDAGData(blueScore, blueWork, selectedParent, mergeSetBlues, mergeSetReds, nil)
|
||||
gh.dataStore.Stage(stagingArea, blockCandidate, blockGHOSTDAGData)
|
||||
return nil
|
||||
}
|
||||
|
||||
/* --------isMoreHash(w, selectedParent)----------------*/
|
||||
func ismoreHash(parent *externalapi.DomainHash, selectedParent *externalapi.DomainHash) bool {
|
||||
func isMoreHash(parent *externalapi.DomainHash, selectedParent *externalapi.DomainHash) bool {
|
||||
parentByteArray := parent.ByteArray()
|
||||
selectedParentByteArray := selectedParent.ByteArray()
|
||||
//Check if parentHash is more then selectedParentHash
|
||||
|
||||
for i := 0; i < len(parentByteArray); i++ {
|
||||
switch {
|
||||
case parentByteArray[i] < selectedParentByteArray[i]:
|
||||
@@ -136,46 +137,39 @@ func ismoreHash(parent *externalapi.DomainHash, selectedParent *externalapi.Doma
|
||||
return false
|
||||
}
|
||||
|
||||
/* 1. blue = selectedParent.blue + blues
|
||||
2. not connected to at most K blocks (from the blue group)
|
||||
3. for each block at blue , check if not destroy
|
||||
*/
|
||||
|
||||
/* ---------------divideBluesReds--------------------- */
|
||||
func (gh *ghostdagHelper) divideBlueRed(stagingArea *model.StagingArea,
|
||||
selectedParent *externalapi.DomainHash, desiredBlock *externalapi.DomainHash,
|
||||
func (gh *ghostdagHelper) divideToBlueAndRed(stagingArea *model.StagingArea, blockToCheck *externalapi.DomainHash,
|
||||
blues *[]*externalapi.DomainHash, reds *[]*externalapi.DomainHash, blueSet *[]*externalapi.DomainHash) error {
|
||||
|
||||
var k = int(gh.k)
|
||||
counter := 0
|
||||
|
||||
var suspectsBlues = make([]*externalapi.DomainHash, 0)
|
||||
anticoneBlocksCounter := 0
|
||||
anticoneBlues := make([]*externalapi.DomainHash, 0)
|
||||
isMergeBlue := true
|
||||
|
||||
//check that not-connected to at most k.
|
||||
for _, block := range *blueSet {
|
||||
isAnticone, err := gh.isAnticone(stagingArea, block, desiredBlock)
|
||||
for _, blueblock := range *blueSet {
|
||||
isAnticone, err := gh.isAnticone(stagingArea, blueblock, blockToCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isAnticone {
|
||||
counter++
|
||||
suspectsBlues = append(suspectsBlues, block)
|
||||
anticoneBlocksCounter++
|
||||
anticoneBlues = append(anticoneBlues, blueblock)
|
||||
}
|
||||
if counter > k {
|
||||
if anticoneBlocksCounter > k {
|
||||
isMergeBlue = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isMergeBlue {
|
||||
if !contains(desiredBlock, *reds) {
|
||||
*reds = append(*reds, desiredBlock)
|
||||
if !contains(blockToCheck, *reds) {
|
||||
*reds = append(*reds, blockToCheck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// check that the k-cluster of each blue is still valid.
|
||||
for _, blue := range suspectsBlues {
|
||||
isDestroyed, err := gh.checkIfDestroy(stagingArea, blue, blueSet)
|
||||
// check that the k-cluster of each anticone blue block is still valid.
|
||||
for _, blueBlock := range anticoneBlues {
|
||||
isDestroyed, err := gh.checkIfDestroy(stagingArea, blueBlock, blueSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,39 +179,37 @@ func (gh *ghostdagHelper) divideBlueRed(stagingArea *model.StagingArea,
|
||||
}
|
||||
}
|
||||
if !isMergeBlue {
|
||||
if !contains(desiredBlock, *reds) {
|
||||
*reds = append(*reds, desiredBlock)
|
||||
if !contains(blockToCheck, *reds) {
|
||||
*reds = append(*reds, blockToCheck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !contains(desiredBlock, *blues) {
|
||||
*blues = append(*blues, desiredBlock)
|
||||
if !contains(blockToCheck, *blues) {
|
||||
*blues = append(*blues, blockToCheck)
|
||||
}
|
||||
if !contains(desiredBlock, *blueSet) {
|
||||
*blueSet = append(*blueSet, desiredBlock)
|
||||
if !contains(blockToCheck, *blueSet) {
|
||||
*blueSet = append(*blueSet, blockToCheck)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* ---------------isAnticone-------------------------- */
|
||||
func (gh *ghostdagHelper) isAnticone(stagingArea *model.StagingArea, blockA, blockB *externalapi.DomainHash) (bool, error) {
|
||||
isAAncestorOfAB, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockA, blockB)
|
||||
isBlockAAncestorOfBlockB, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockA, blockB)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isBAncestorOfA, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockB, blockA)
|
||||
isBlockBAncestorOfBlockA, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, blockB, blockA)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !isAAncestorOfAB && !isBAncestorOfA, nil
|
||||
|
||||
return !isBlockAAncestorOfBlockB && !isBlockBAncestorOfBlockA, nil
|
||||
}
|
||||
|
||||
/* ----------------validateKCluster------------------- */
|
||||
func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain *externalapi.DomainHash,
|
||||
checkedBlock *externalapi.DomainHash, counter *int, blueSet *[]*externalapi.DomainHash) (bool, error) {
|
||||
func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain *externalapi.DomainHash, checkedBlock *externalapi.DomainHash,
|
||||
counter *int, blueSet *[]*externalapi.DomainHash) (bool, error) {
|
||||
|
||||
var k = int(gh.k)
|
||||
k := int(gh.k)
|
||||
isAnticone, err := gh.isAnticone(stagingArea, chain, checkedBlock)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -236,6 +228,7 @@ func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain
|
||||
*counter++
|
||||
return true, nil
|
||||
}
|
||||
|
||||
isAncestorOf, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, checkedBlock, chain)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -251,27 +244,24 @@ func (gh *ghostdagHelper) validateKCluster(stagingArea *model.StagingArea, chain
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
/*----------------contains-------------------------- */
|
||||
func contains(item *externalapi.DomainHash, items []*externalapi.DomainHash) bool {
|
||||
for _, r := range items {
|
||||
if r.Equal(item) {
|
||||
func contains(desiredItem *externalapi.DomainHash, items []*externalapi.DomainHash) bool {
|
||||
for _, item := range items {
|
||||
if item.Equal(desiredItem) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/* ----------------checkIfDestroy------------------- */
|
||||
/* find number of not-connected in his blue*/
|
||||
// find number of not-connected in blueSet
|
||||
func (gh *ghostdagHelper) checkIfDestroy(stagingArea *model.StagingArea, blockBlue *externalapi.DomainHash,
|
||||
blueSet *[]*externalapi.DomainHash) (bool, error) {
|
||||
|
||||
// Goal: check that the K-cluster of each block in the blueSet is not destroyed when adding the block to the mergeSet.
|
||||
var k = int(gh.k)
|
||||
// check that the K-cluster of each block in the blueSet is not destroyed when adding the block to the mergeSet.
|
||||
k := int(gh.k)
|
||||
counter := 0
|
||||
for _, blue := range *blueSet {
|
||||
isAnticone, err := gh.isAnticone(stagingArea, blue, blockBlue)
|
||||
@@ -288,51 +278,45 @@ func (gh *ghostdagHelper) checkIfDestroy(stagingArea *model.StagingArea, blockBl
|
||||
return false, nil
|
||||
}
|
||||
|
||||
/* ----------------findMergeSet------------------- */
|
||||
func (gh *ghostdagHelper) findMergeSet(stagingArea *model.StagingArea, parents []*externalapi.DomainHash,
|
||||
selectedParent *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
|
||||
allMergeSet := make([]*externalapi.DomainHash, 0)
|
||||
blockQueue := make([]*externalapi.DomainHash, 0)
|
||||
mergeSet := make([]*externalapi.DomainHash, 0)
|
||||
blocksQueue := make([]*externalapi.DomainHash, 0)
|
||||
for _, parent := range parents {
|
||||
if !contains(parent, blockQueue) {
|
||||
blockQueue = append(blockQueue, parent)
|
||||
if !contains(parent, blocksQueue) {
|
||||
blocksQueue = append(blocksQueue, parent)
|
||||
}
|
||||
|
||||
}
|
||||
for len(blockQueue) > 0 {
|
||||
block := blockQueue[0]
|
||||
blockQueue = blockQueue[1:]
|
||||
for len(blocksQueue) > 0 {
|
||||
block := blocksQueue[0]
|
||||
blocksQueue = blocksQueue[1:]
|
||||
if selectedParent.Equal(block) {
|
||||
if !contains(block, allMergeSet) {
|
||||
allMergeSet = append(allMergeSet, block)
|
||||
if !contains(block, mergeSet) {
|
||||
mergeSet = append(mergeSet, block)
|
||||
}
|
||||
continue
|
||||
}
|
||||
isancestorOf, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, block, selectedParent)
|
||||
isAncestorOf, err := gh.dagTopologyManager.IsAncestorOf(stagingArea, block, selectedParent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isancestorOf {
|
||||
if isAncestorOf {
|
||||
continue
|
||||
}
|
||||
if !contains(block, allMergeSet) {
|
||||
allMergeSet = append(allMergeSet, block)
|
||||
if !contains(block, mergeSet) {
|
||||
mergeSet = append(mergeSet, block)
|
||||
}
|
||||
err = gh.insertParent(stagingArea, block, &blockQueue)
|
||||
err = gh.insertParent(stagingArea, block, &blocksQueue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return allMergeSet, nil
|
||||
return mergeSet, nil
|
||||
}
|
||||
|
||||
/* ----------------insertParent------------------- */
|
||||
/* Insert all parents to the queue*/
|
||||
func (gh *ghostdagHelper) insertParent(stagingArea *model.StagingArea, child *externalapi.DomainHash,
|
||||
queue *[]*externalapi.DomainHash) error {
|
||||
|
||||
// Insert all parents to the queue
|
||||
func (gh *ghostdagHelper) insertParent(stagingArea *model.StagingArea, child *externalapi.DomainHash, queue *[]*externalapi.DomainHash) error {
|
||||
parents, err := gh.dagTopologyManager.Parents(stagingArea, child)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -346,8 +330,9 @@ func (gh *ghostdagHelper) insertParent(stagingArea *model.StagingArea, child *ex
|
||||
return nil
|
||||
}
|
||||
|
||||
/* ----------------findBlueSet------------------- */
|
||||
func (gh *ghostdagHelper) findBlueSet(stagingArea *model.StagingArea, blueSet *[]*externalapi.DomainHash, selectedParent *externalapi.DomainHash) error {
|
||||
func (gh *ghostdagHelper) findBlueSet(stagingArea *model.StagingArea, blueSet *[]*externalapi.DomainHash,
|
||||
selectedParent *externalapi.DomainHash) error {
|
||||
|
||||
for selectedParent != nil {
|
||||
if !contains(selectedParent, *blueSet) {
|
||||
*blueSet = append(*blueSet, selectedParent)
|
||||
@@ -368,44 +353,41 @@ func (gh *ghostdagHelper) findBlueSet(stagingArea *model.StagingArea, blueSet *[
|
||||
return nil
|
||||
}
|
||||
|
||||
/* ----------------sortByBlueScore------------------- */
|
||||
func (gh *ghostdagHelper) sortByBlueWork(stagingArea *model.StagingArea, arr []*externalapi.DomainHash) error {
|
||||
|
||||
var err error = nil
|
||||
var err error
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
|
||||
blockLeft, error := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[i])
|
||||
if error != nil {
|
||||
err = error
|
||||
blockLeft, err := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[i])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
blockRight, error := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[j])
|
||||
if error != nil {
|
||||
err = error
|
||||
blockRight, err := gh.dataStore.Get(gh.dbAccess, stagingArea, arr[j])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if blockLeft.BlueWork().Cmp(blockRight.BlueWork()) == -1 {
|
||||
return true
|
||||
}
|
||||
if blockLeft.BlueWork().Cmp(blockRight.BlueWork()) == 0 {
|
||||
return ismoreHash(arr[j], arr[i])
|
||||
return isMoreHash(arr[j], arr[i])
|
||||
}
|
||||
return false
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
/* --------------------------------------------- */
|
||||
|
||||
func (gh *ghostdagHelper) BlockData(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*model.BlockGHOSTDAGData, error) {
|
||||
return gh.dataStore.Get(gh.dbAccess, stagingArea, blockHash)
|
||||
}
|
||||
func (gh *ghostdagHelper) ChooseSelectedParent(stagingArea *model.StagingArea, blockHashes ...*externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||
|
||||
func (gh *ghostdagHelper) ChooseSelectedParent(*model.StagingArea, ...*externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (gh *ghostdagHelper) Less(blockHashA *externalapi.DomainHash, ghostdagDataA *model.BlockGHOSTDAGData, blockHashB *externalapi.DomainHash, ghostdagDataB *model.BlockGHOSTDAGData) bool {
|
||||
func (gh *ghostdagHelper) Less(*externalapi.DomainHash, *model.BlockGHOSTDAGData, *externalapi.DomainHash, *model.BlockGHOSTDAGData) bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (gh *ghostdagHelper) GetSortedMergeSet(*model.StagingArea, *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func (v *transactionValidator) validateTransactionScripts(tx *externalapi.Domain
|
||||
}
|
||||
|
||||
scriptPubKey := utxoEntry.ScriptPublicKey()
|
||||
vm, err := txscript.NewEngine(scriptPubKey, tx, i, txscript.ScriptNoFlags, v.sigCache, sighashReusedValues)
|
||||
vm, err := txscript.NewEngine(scriptPubKey, tx, i, txscript.ScriptNoFlags, v.sigCache, v.sigCacheECDSA, sighashReusedValues)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ruleerrors.ErrScriptMalformed, "failed to parse input "+
|
||||
"%d which references output %s - "+
|
||||
|
||||
@@ -21,6 +21,7 @@ type transactionValidator struct {
|
||||
massPerSigOp uint64
|
||||
maxCoinbasePayloadLength uint64
|
||||
sigCache *txscript.SigCache
|
||||
sigCacheECDSA *txscript.SigCacheECDSA
|
||||
}
|
||||
|
||||
// New instantiates a new TransactionValidator
|
||||
@@ -47,5 +48,6 @@ func New(blockCoinbaseMaturity uint64,
|
||||
ghostdagDataStore: ghostdagDataStore,
|
||||
daaBlocksStore: daaBlocksStore,
|
||||
sigCache: txscript.NewSigCache(sigCacheSize),
|
||||
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,9 @@ func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pkh address: %v", err)
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
@@ -261,9 +261,9 @@ func TestSigningTwoInputs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pkh address: %v", err)
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
@@ -362,3 +362,127 @@ func TestSigningTwoInputs(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSigningTwoInputsECDSA(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestSigningTwoInputsECDSA")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
privateKey, err := secp256k1.GenerateECDSAPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a private key: %v", err)
|
||||
}
|
||||
publicKey, err := privateKey.ECDSAPublicKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a public key: %v", err)
|
||||
}
|
||||
publicKeySerialized, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPublicKeyECDSA(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block2Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2, err := tc.GetBlock(block2Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block2: %+v", err)
|
||||
}
|
||||
|
||||
block3, err := tc.GetBlock(block3Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block3: %+v", err)
|
||||
}
|
||||
|
||||
block2Tx := block2.Transactions[0]
|
||||
block2TxOut := block2Tx.Outputs[0]
|
||||
|
||||
block3Tx := block3.Transactions[0]
|
||||
block3TxOut := block3Tx.Outputs[0]
|
||||
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block2.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block2TxOut.Value, block2TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block3.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block3TxOut.Value, block3TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{
|
||||
Value: 1,
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
Gas: 0,
|
||||
LockTime: 0,
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, input := range tx.Inputs {
|
||||
signatureScript, err := txscript.SignatureScriptECDSA(tx, i, consensushashing.SigHashAll, privateKey,
|
||||
sighashReusedValues)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a sigScript: %v", err)
|
||||
}
|
||||
input.SignatureScript = signatureScript
|
||||
}
|
||||
|
||||
_, insertionResult, err := tc.AddBlock([]*externalapi.DomainHash{block3Hash}, nil, []*externalapi.DomainTransaction{tx})
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
txOutpoint := &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(tx),
|
||||
Index: 0,
|
||||
}
|
||||
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(txOutpoint) {
|
||||
t.Fatalf("tx was not accepted by the DAG")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ type testConsensus struct {
|
||||
testReachabilityManager testapi.TestReachabilityManager
|
||||
testConsensusStateManager testapi.TestConsensusStateManager
|
||||
testTransactionValidator testapi.TestTransactionValidator
|
||||
|
||||
buildBlockConsensus *consensus
|
||||
}
|
||||
|
||||
func (tc *testConsensus) DAGParams() *dagconfig.Params {
|
||||
|
||||
@@ -2,7 +2,6 @@ package consensushashing
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
@@ -58,10 +57,10 @@ type SighashReusedValues struct {
|
||||
payloadHash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// CalculateSignatureHash will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification.
|
||||
// CalculateSignatureHashSchnorr will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification for Schnorr.
|
||||
// This returns error only if one of the provided parameters are consensus-invalid.
|
||||
func CalculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
func CalculateSignatureHashSchnorr(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
|
||||
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
@@ -70,18 +69,26 @@ func CalculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, h
|
||||
|
||||
txIn := tx.Inputs[inputIndex]
|
||||
prevScriptPublicKey := txIn.UTXOEntry.ScriptPublicKey()
|
||||
|
||||
if tx.Version > constants.MaxTransactionVersion {
|
||||
return nil, errors.Errorf("Transaction version is unknown.")
|
||||
}
|
||||
|
||||
if prevScriptPublicKey.Version > constants.MaxScriptPublicKeyVersion {
|
||||
return nil, errors.Errorf("Script version is unknown.")
|
||||
}
|
||||
|
||||
return calculateSignatureHash(tx, inputIndex, txIn, prevScriptPublicKey, hashType, reusedValues)
|
||||
}
|
||||
|
||||
// CalculateSignatureHashECDSA will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification for ECDSA.
|
||||
// This returns error only if one of the provided parameters are consensus-invalid.
|
||||
func CalculateSignatureHashECDSA(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
|
||||
|
||||
hash, err := CalculateSignatureHashSchnorr(tx, inputIndex, hashType, reusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashWriter := hashes.NewTransactionSigningHashECDSAWriter()
|
||||
hashWriter.InfallibleWrite(hash.ByteSlice())
|
||||
|
||||
return hashWriter.Finalize(), nil
|
||||
}
|
||||
|
||||
func calculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, txIn *externalapi.DomainTransactionInput,
|
||||
prevScriptPublicKey *externalapi.ScriptPublicKey, hashType SigHashType, reusedValues *SighashReusedValues) (
|
||||
*externalapi.DomainHash, error) {
|
||||
|
||||
@@ -88,7 +88,7 @@ func modifySubnetworkID(tx *externalapi.DomainTransaction) *externalapi.DomainTr
|
||||
return clone
|
||||
}
|
||||
|
||||
func TestCalculateSignatureHash(t *testing.T) {
|
||||
func TestCalculateSignatureHashSchnorr(t *testing.T) {
|
||||
nativeTx, subnetworkTx, err := generateTxs()
|
||||
if err != nil {
|
||||
t.Fatalf("Error from generateTxs: %+v", err)
|
||||
@@ -108,86 +108,86 @@ func TestCalculateSignatureHash(t *testing.T) {
|
||||
|
||||
// sigHashAll
|
||||
{name: "native-all-0", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "c899a5ea7414f0bbfd77e50674f46da34ce8722b928d4362a4b4b727c69c6499"},
|
||||
expectedSignatureHash: "3e4a8effa6903dea5f762754ec410d732114ec2907e5bcbae7b6dd8d3f309a10"},
|
||||
{name: "native-all-0-modify-input-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // should change the hash
|
||||
expectedSignatureHash: "faf3b9db2e07b1c14b2df02002d3e40f1e430f177ac5cd3354c84dad8fbe72ce"},
|
||||
expectedSignatureHash: "0bd2947383101f9708d94d5799626c69c1b8472d2de66523c90c4a674e99cc51"},
|
||||
{name: "native-all-0-modify-output-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // should change the hash
|
||||
expectedSignatureHash: "3a557c5b873aab72dcb81649642e1d7a63b75dcdcc74e19d340964a9e0eac76c"},
|
||||
expectedSignatureHash: "ae69aa1372e958f069bf52d2628ead7ee789f195340b615ff0bcb6f01a995810"},
|
||||
{name: "native-all-0-modify-sequence-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // should change the hash
|
||||
expectedSignatureHash: "2dd5fe8f9fa4bf551ea2f080a26e07b2462083e12d3b2ed01cb9369a61920665"},
|
||||
expectedSignatureHash: "51295575e7efc1fe1c2e741ade49089e3d780d15d26e9a0b18b3d90e35caf795"},
|
||||
{name: "native-all-anyonecanpay-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "19fe2e0db681017f318fda705a39bbbad9c1085514cfbcff6fac01e1725f758b"},
|
||||
expectedSignatureHash: "0a13d3cab42a6cf3a7ef96f7b28c9bb0d98c209a3032ff85a6bfa7dac520f2c2"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(0), // should change the hash
|
||||
expectedSignatureHash: "5b21d492560a1c794595f769b3ae3c151775b9cfc4029d17c53f1856e1005da4"},
|
||||
expectedSignatureHash: "aeaa56f5bc99daea0be5ed4b047808ce0123a81ac89c6089b72fa7199f5cd1c6"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-1", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "19fe2e0db681017f318fda705a39bbbad9c1085514cfbcff6fac01e1725f758b"},
|
||||
expectedSignatureHash: "0a13d3cab42a6cf3a7ef96f7b28c9bb0d98c209a3032ff85a6bfa7dac520f2c2"},
|
||||
{name: "native-all-anyonecanpay-0-modify-sequence", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "19fe2e0db681017f318fda705a39bbbad9c1085514cfbcff6fac01e1725f758b"},
|
||||
expectedSignatureHash: "0a13d3cab42a6cf3a7ef96f7b28c9bb0d98c209a3032ff85a6bfa7dac520f2c2"},
|
||||
|
||||
// sigHashNone
|
||||
{name: "native-none-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
expectedSignatureHash: "fafabaabf6349fee4e18626b4eff015472f2317576a8f4bf7b0eea1df6f3e32b"},
|
||||
expectedSignatureHash: "da53f7c726b55357adb1b644265fe7b9b7897cadde91a942d6127195c2ce99dc"},
|
||||
{name: "native-none-0-modify-output-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "fafabaabf6349fee4e18626b4eff015472f2317576a8f4bf7b0eea1df6f3e32b"},
|
||||
expectedSignatureHash: "da53f7c726b55357adb1b644265fe7b9b7897cadde91a942d6127195c2ce99dc"},
|
||||
{name: "native-none-0-modify-sequence-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "daee0700e0ed4ab9f50de24d83e0bfce62999474ec8ceeb537ea35980662b601"},
|
||||
expectedSignatureHash: "72c07c152a3792fb863d2de219ab4e863fe6779dc970a5c3958e26b3a3b9f139"},
|
||||
{name: "native-none-0-modify-sequence-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "fafabaabf6349fee4e18626b4eff015472f2317576a8f4bf7b0eea1df6f3e32b"},
|
||||
expectedSignatureHash: "da53f7c726b55357adb1b644265fe7b9b7897cadde91a942d6127195c2ce99dc"},
|
||||
{name: "native-none-anyonecanpay-0", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "4e5c2d895f9711dc89c19d49ba478e9c8f4be0d82c9bd6b60d0361eb9b5296bc"},
|
||||
expectedSignatureHash: "18eb79df328a516708f792cadfc4bf2be21b6add35fb6637a20347d532b1dce1"},
|
||||
{name: "native-none-anyonecanpay-0-modify-amount-spent", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyAmountSpent(0), // should change the hash
|
||||
expectedSignatureHash: "9ce2f75eafc85b8e19133942c3143d14b61f2e7cc479fbc6d2fca026e50897f1"},
|
||||
expectedSignatureHash: "674a8e6effa0bbfdb3df3ca45a7b17ab695cc0f9b3e0519e5bff165de13604e2"},
|
||||
{name: "native-none-anyonecanpay-0-modify-script-public-key", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyScriptPublicKey(0), // should change the hash
|
||||
expectedSignatureHash: "c6c364190520fe6c0419c2f45e25bf084356333b03ac7aaec28251126398bda3"},
|
||||
expectedSignatureHash: "7a879d339c9b948c5264f1c0b8463f551221b2dcd575623f178cbbfcf7e01987"},
|
||||
|
||||
// sigHashSingle
|
||||
{name: "native-single-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
expectedSignatureHash: "6ff01d5d7cd82e24bc9ca0edec8bd6931ffb5aa1d303f07ca05dc89757343a92"},
|
||||
expectedSignatureHash: "99c0a4a381fed9dbd651450d6ca969a6e8cdd941e9d73be086a1ba6c90fb630f"},
|
||||
{name: "native-single-0-modify-output-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(0), // should change the hash
|
||||
expectedSignatureHash: "d62af956aea369365bacc7e7f1aac106836994f1648311e82dd38da822c8771e"},
|
||||
expectedSignatureHash: "92189a32391ca50a454d1853efed55acb1c49993911a1201df9891c866c483ee"},
|
||||
{name: "native-single-0-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6ff01d5d7cd82e24bc9ca0edec8bd6931ffb5aa1d303f07ca05dc89757343a92"},
|
||||
expectedSignatureHash: "99c0a4a381fed9dbd651450d6ca969a6e8cdd941e9d73be086a1ba6c90fb630f"},
|
||||
{name: "native-single-0-modify-sequence-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "46692229d45bf2ceacb18960faba29753e325c0ade26ecf94495b91daacb828d"},
|
||||
expectedSignatureHash: "765e2289df98b3a5269f112d4b36d57fe32c74d42e04a3046c6a3c8dd78a4121"},
|
||||
{name: "native-single-0-modify-sequence-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6ff01d5d7cd82e24bc9ca0edec8bd6931ffb5aa1d303f07ca05dc89757343a92"},
|
||||
expectedSignatureHash: "99c0a4a381fed9dbd651450d6ca969a6e8cdd941e9d73be086a1ba6c90fb630f"},
|
||||
{name: "native-single-2-no-corresponding-output", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
expectedSignatureHash: "d3cc385082a7f272ec2c8aae7f3a96ab2f49a4a4e1ed44d61af34058a7721281"},
|
||||
expectedSignatureHash: "43de18c04d7fde81f49a40228d8730b4ceb0c66c77841c22622f59554769dd13"},
|
||||
{name: "native-single-2-no-corresponding-output-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "d3cc385082a7f272ec2c8aae7f3a96ab2f49a4a4e1ed44d61af34058a7721281"},
|
||||
expectedSignatureHash: "43de18c04d7fde81f49a40228d8730b4ceb0c66c77841c22622f59554769dd13"},
|
||||
{name: "native-single-anyonecanpay-0", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "408fcfd8ceca135c0f54569ccf8ac727e1aa6b5a15f87ccca765f1d5808aa4ea"},
|
||||
expectedSignatureHash: "2e270f86fd68f780a5aa8d250364ab6786d990040268e5d561d09dde365dfab7"},
|
||||
{name: "native-single-anyonecanpay-2-no-corresponding-output", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 2,
|
||||
expectedSignatureHash: "685fac0d0b9dd3c5556f266714c4f7f93475d49fa12befb18e8297bc062aeaba"},
|
||||
expectedSignatureHash: "afc05dd6b4a530cc3a4d7d23126548bd1f31c793e9282cbbfa27ff5566219501"},
|
||||
|
||||
// subnetwork transaction
|
||||
{name: "subnetwork-all-0", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "0e8b1433b761a220a61c0dc1f0fda909d49cef120d98d9f87344fef52dac0d8b"},
|
||||
expectedSignatureHash: "5a67423ee048e067b94e2d4fc2960f62eceff6aa8b6c5ad71e3b7b7e5ff6bad7"},
|
||||
{name: "subnetwork-all-modify-payload", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyPayload, // should change the hash
|
||||
expectedSignatureHash: "087315acb9193eaa14929dbe3d0ace80238aebe13eab3bf8db6c0a0d7ddb782e"},
|
||||
expectedSignatureHash: "0d3bc5914da8dc8df081945fea1255359f380ca9baa8b31dfb3657c1e3c6038a"},
|
||||
{name: "subnetwork-all-modify-gas", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyGas, // should change the hash
|
||||
expectedSignatureHash: "07a90408ef45864ae8354b07a74cf826a4621391425ba417470a6e680af4ce70"},
|
||||
expectedSignatureHash: "70abe9f947d0a0f5d735f9a063db8af41fe5902940f2693a1782119063097094"},
|
||||
{name: "subnetwork-all-subnetwork-id", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySubnetworkID, // should change the hash
|
||||
expectedSignatureHash: "4ca44c2e35729ae5efe831a77027f1a58a41dbdd853459c26cbfe7d6c88783fb"},
|
||||
expectedSignatureHash: "571a0b7ea905b7a6ff7ab825b72d23f911bac0bfaa7c4c97a4887a3d090925d4"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -196,10 +196,132 @@ func TestCalculateSignatureHash(t *testing.T) {
|
||||
tx = test.modificationFunction(tx)
|
||||
}
|
||||
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHash(
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHashSchnorr(
|
||||
tx, test.inputIndex, test.hashType, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("%s: Error from CalculateSignatureHash: %+v", test.name, err)
|
||||
t.Errorf("%s: Error from CalculateSignatureHashSchnorr: %+v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if actualSignatureHash.String() != test.expectedSignatureHash {
|
||||
t.Errorf("%s: expected signature hash: '%s'; but got: '%s'",
|
||||
test.name, test.expectedSignatureHash, actualSignatureHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateSignatureHashECDSA(t *testing.T) {
|
||||
nativeTx, subnetworkTx, err := generateTxs()
|
||||
if err != nil {
|
||||
t.Fatalf("Error from generateTxs: %+v", err)
|
||||
}
|
||||
|
||||
// Note: Expected values were generated by the same code that they test,
|
||||
// As long as those were not verified using 3rd-party code they only check for regression, not correctness
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *externalapi.DomainTransaction
|
||||
hashType consensushashing.SigHashType
|
||||
inputIndex int
|
||||
modificationFunction func(*externalapi.DomainTransaction) *externalapi.DomainTransaction
|
||||
expectedSignatureHash string
|
||||
}{
|
||||
// native transactions
|
||||
|
||||
// sigHashAll
|
||||
{name: "native-all-0", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "150a2bcd0296f76a3395a4a9982df46bf24ce93f955bc39c10ffc95b6c524eb3"},
|
||||
{name: "native-all-0-modify-input-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // should change the hash
|
||||
expectedSignatureHash: "8fb5304e181b003e0c123ea6f6677abc3704feec47054e8a1c218b827bf57ca0"},
|
||||
{name: "native-all-0-modify-output-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // should change the hash
|
||||
expectedSignatureHash: "180cb36454aa80822694decde4fc711104e35a4bddf92286a83877f2b8d0aabb"},
|
||||
{name: "native-all-0-modify-sequence-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // should change the hash
|
||||
expectedSignatureHash: "5b5f1c42a3c3c16bb4922777e2963c00e6a2cce39afa1980d2288053378f9632"},
|
||||
{name: "native-all-anyonecanpay-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(0), // should change the hash
|
||||
expectedSignatureHash: "1208491d564c138d613f51b997394dbad23feca7c0ca88c7f36cdf6b9173327d"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-1", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
{name: "native-all-anyonecanpay-0-modify-sequence", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
|
||||
// sigHashNone
|
||||
{name: "native-none-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-0-modify-output-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-0-modify-sequence-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "57d76e2568cd3fc3426b4f8836fe900a2d20e740fad744949126651fd549f75e"},
|
||||
{name: "native-none-0-modify-sequence-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-anyonecanpay-0", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "ef97a0f89d623302619f9aa2a00fce1522e72d4d255e6c6d3ed225ffc02f38ff"},
|
||||
{name: "native-none-anyonecanpay-0-modify-amount-spent", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyAmountSpent(0), // should change the hash
|
||||
expectedSignatureHash: "043a2a943f02607be126ac6609ab2324aae389d784a4147f27101e7da379311a"},
|
||||
{name: "native-none-anyonecanpay-0-modify-script-public-key", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyScriptPublicKey(0), // should change the hash
|
||||
expectedSignatureHash: "f2cd43d0d047cdcfbf8b6e12a86cfbf250f1e2c34dc5e631675a5f5b867bd9e6"},
|
||||
|
||||
// sigHashSingle
|
||||
{name: "native-single-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-0-modify-output-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(0), // should change the hash
|
||||
expectedSignatureHash: "c2c7e77516a15f0f47f886b14cc47af2045eea15f176a9a560a9d47d8866958f"},
|
||||
{name: "native-single-0-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-0-modify-sequence-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "2034eec2acc08c49d3896cc1bda214904ca850fc5989518885465b5a3154ee7f"},
|
||||
{name: "native-single-0-modify-sequence-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-2-no-corresponding-output", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
expectedSignatureHash: "84ae3bb03202efc587d97e5aea7b80581b82242b969e6dea13b8daa32d24c0c1"},
|
||||
{name: "native-single-2-no-corresponding-output-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "84ae3bb03202efc587d97e5aea7b80581b82242b969e6dea13b8daa32d24c0c1"},
|
||||
{name: "native-single-anyonecanpay-0", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "b2ccf259a65c3231d741a03420967b95563c3928cc15d3d15e8e795f383ab48b"},
|
||||
{name: "native-single-anyonecanpay-2-no-corresponding-output", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 2,
|
||||
expectedSignatureHash: "652c8cd0ac050e41aad347ea09ee788360eec70908ba22fe5bba5bdde49b8ae1"},
|
||||
|
||||
// subnetwork transaction
|
||||
{name: "subnetwork-all-0", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "2e828c04f5f03e4ce4b3de1fa5303400da5fa504291b760f5f6d4e98fc24597f"},
|
||||
{name: "subnetwork-all-modify-payload", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyPayload, // should change the hash
|
||||
expectedSignatureHash: "d5f3993aa8b7f47df52f78f2be9965f928c9cca9ac9e9542f1190b9d5ed6c17d"},
|
||||
{name: "subnetwork-all-modify-gas", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyGas, // should change the hash
|
||||
expectedSignatureHash: "e74d4a9fa5cdf476299ebdfa03f3c8021a157f814731ea11f6a6d606dc5cd439"},
|
||||
{name: "subnetwork-all-subnetwork-id", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySubnetworkID, // should change the hash
|
||||
expectedSignatureHash: "ca8bf9bc42cda2ec3ce8bee090011072e56ff4d0d8616d5c20cefe5f84d7fb37"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tx := test.tx
|
||||
if test.modificationFunction != nil {
|
||||
tx = test.modificationFunction(tx)
|
||||
}
|
||||
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHashECDSA(
|
||||
tx, test.inputIndex, test.hashType, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("%s: Error from CalculateSignatureHashECDSA: %+v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -283,7 +405,7 @@ func generateTxs() (nativeTx, subnetworkTx *externalapi.DomainTransaction, err e
|
||||
return nativeTx, subnetworkTx, nil
|
||||
}
|
||||
|
||||
func BenchmarkCalculateSignatureHash(b *testing.B) {
|
||||
func BenchmarkCalculateSignatureHashSchnorr(b *testing.B) {
|
||||
sigHashTypes := []consensushashing.SigHashType{
|
||||
consensushashing.SigHashAll,
|
||||
consensushashing.SigHashNone,
|
||||
@@ -300,9 +422,9 @@ func BenchmarkCalculateSignatureHash(b *testing.B) {
|
||||
reusedValues := &consensushashing.SighashReusedValues{}
|
||||
for inputIndex := range tx.Inputs {
|
||||
sigHashType := sigHashTypes[inputIndex%len(sigHashTypes)]
|
||||
_, err := consensushashing.CalculateSignatureHash(tx, inputIndex, sigHashType, reusedValues)
|
||||
_, err := consensushashing.CalculateSignatureHashSchnorr(tx, inputIndex, sigHashType, reusedValues)
|
||||
if err != nil {
|
||||
b.Fatalf("Error from CalculateSignatureHash: %+v", err)
|
||||
b.Fatalf("Error from CalculateSignatureHashSchnorr: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,28 +22,6 @@ const (
|
||||
txEncodingExcludeSignatureScript = 1 << iota
|
||||
)
|
||||
|
||||
// TransactionHashForSigning hashes the transaction and the given hash type in a way that is intended for
|
||||
// signatures.
|
||||
func TransactionHashForSigning(tx *externalapi.DomainTransaction, hashType uint32) *externalapi.DomainHash {
|
||||
// Encode the header and hash everything prior to the number of
|
||||
// transactions.
|
||||
writer := hashes.NewTransactionSigningHashWriter()
|
||||
err := serializeTransaction(writer, tx, txEncodingFull)
|
||||
if err != nil {
|
||||
// It seems like this could only happen if the writer returned an error.
|
||||
// and this writer should never return an error (no allocations or possible failures)
|
||||
// the only non-writer error path here is unknown types in `WriteElement`
|
||||
panic(errors.Wrap(err, "TransactionHashForSigning() failed. this should never fail for structurally-valid transactions"))
|
||||
}
|
||||
|
||||
err = serialization.WriteElement(writer, hashType)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "this should never happen. Hash digest should never return an error"))
|
||||
}
|
||||
|
||||
return writer.Finalize()
|
||||
}
|
||||
|
||||
// TransactionHash returns the transaction hash.
|
||||
func TransactionHash(tx *externalapi.DomainTransaction) *externalapi.DomainHash {
|
||||
// Encode the header and hash everything prior to the number of
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
package hashes
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
transcationHashDomain = "TransactionHash"
|
||||
transcationIDDomain = "TransactionID"
|
||||
transcationSigningDomain = "TransactionSigningHash"
|
||||
blockDomain = "BlockHash"
|
||||
proofOfWorkDomain = "ProofOfWorkHash"
|
||||
merkleBranchDomain = "MerkleBranchHash"
|
||||
transcationHashDomain = "TransactionHash"
|
||||
transcationIDDomain = "TransactionID"
|
||||
transcationSigningDomain = "TransactionSigningHash"
|
||||
transcationSigningECDSADomain = "TransactionSigningHashECDSA"
|
||||
blockDomain = "BlockHash"
|
||||
proofOfWorkDomain = "ProofOfWorkHash"
|
||||
merkleBranchDomain = "MerkleBranchHash"
|
||||
)
|
||||
|
||||
// transactionSigningECDSADomainHash is a hashed version of transcationSigningECDSADomain that is used
|
||||
// to make it a constant size. This is needed because this domain is used by sha256 hash writer, and
|
||||
// sha256 doesn't support variable size domain separation.
|
||||
var transactionSigningECDSADomainHash = sha256.Sum256([]byte(transcationSigningECDSADomain))
|
||||
|
||||
// NewTransactionHashWriter Returns a new HashWriter used for transaction hashes
|
||||
func NewTransactionHashWriter() HashWriter {
|
||||
blake, err := blake2b.New256([]byte(transcationHashDomain))
|
||||
@@ -41,6 +48,13 @@ func NewTransactionSigningHashWriter() HashWriter {
|
||||
return HashWriter{blake}
|
||||
}
|
||||
|
||||
// NewTransactionSigningHashECDSAWriter Returns a new HashWriter used for signing on a transaction with ECDSA
|
||||
func NewTransactionSigningHashECDSAWriter() HashWriter {
|
||||
hashWriter := HashWriter{sha256.New()}
|
||||
hashWriter.InfallibleWrite(transactionSigningECDSADomainHash[:])
|
||||
return hashWriter
|
||||
}
|
||||
|
||||
// NewBlockHashWriter Returns a new HashWriter used for hashing blocks
|
||||
func NewBlockHashWriter() HashWriter {
|
||||
blake, err := blake2b.New256([]byte(blockDomain))
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package hashes
|
||||
|
||||
import (
|
||||
"hash"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/pkg/errors"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// HashWriter is used to incrementally hash data without concatenating all of the data to a single buffer
|
||||
|
||||
@@ -15,7 +15,7 @@ although it is still fairly powerful.
|
||||
|
||||
## Examples
|
||||
|
||||
* [Standard Pay-to-pubkey-hash Script](http://godoc.org/github.com/kaspanet/kaspad/txscript#example-PayToAddrScript)
|
||||
* [Standard Pay-to-pubkey Script](http://godoc.org/github.com/kaspanet/kaspad/txscript#example-PayToAddrScript)
|
||||
Demonstrates creating a script which pays to a kaspa address. It also
|
||||
prints the created script hex and uses the DisasmString function to display
|
||||
the disassembled script.
|
||||
|
||||
@@ -53,6 +53,7 @@ type Engine struct {
|
||||
numOps int
|
||||
flags ScriptFlags
|
||||
sigCache *SigCache
|
||||
sigCacheECDSA *SigCacheECDSA
|
||||
sigHashReusedValues *consensushashing.SighashReusedValues
|
||||
isP2SH bool // treat execution as pay-to-script-hash
|
||||
savedFirstStack [][]byte // stack from first script for ps2h scripts
|
||||
@@ -369,6 +370,14 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
||||
return scriptError(ErrPubKeyFormat, "unsupported public key type")
|
||||
}
|
||||
|
||||
func (vm *Engine) checkPubKeyEncodingECDSA(pubKey []byte) error {
|
||||
if len(pubKey) == 33 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scriptError(ErrPubKeyFormat, "unsupported public key type")
|
||||
}
|
||||
|
||||
// checkSignatureLength returns whether or not the passed signature is
|
||||
// in the correct Schnorr format.
|
||||
func (vm *Engine) checkSignatureLength(sig []byte) error {
|
||||
@@ -379,6 +388,14 @@ func (vm *Engine) checkSignatureLength(sig []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *Engine) checkSignatureLengthECDSA(sig []byte) error {
|
||||
if len(sig) != 64 {
|
||||
message := fmt.Sprintf("invalid signature length %d", len(sig))
|
||||
return scriptError(ErrSigLength, message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStack returns the contents of stack as a byte array bottom up
|
||||
func getStack(stack *stack) [][]byte {
|
||||
array := make([][]byte, stack.Depth())
|
||||
@@ -428,7 +445,7 @@ func (vm *Engine) SetAltStack(data [][]byte) {
|
||||
// transaction, and input index. The flags modify the behavior of the script
|
||||
// engine according to the description provided by each flag.
|
||||
func NewEngine(scriptPubKey *externalapi.ScriptPublicKey, tx *externalapi.DomainTransaction, txIdx int, flags ScriptFlags,
|
||||
sigCache *SigCache, sighashReusedValues *consensushashing.SighashReusedValues) (*Engine, error) {
|
||||
sigCache *SigCache, sigCacheECDSA *SigCacheECDSA, sighashReusedValues *consensushashing.SighashReusedValues) (*Engine, error) {
|
||||
|
||||
// The provided transaction input index must refer to a valid input.
|
||||
if txIdx < 0 || txIdx >= len(tx.Inputs) {
|
||||
@@ -446,7 +463,7 @@ func NewEngine(scriptPubKey *externalapi.ScriptPublicKey, tx *externalapi.Domain
|
||||
return nil, scriptError(ErrEvalFalse,
|
||||
"false stack entry at end of script execution")
|
||||
}
|
||||
vm := Engine{scriptVersion: scriptPubKey.Version, flags: flags, sigCache: sigCache}
|
||||
vm := Engine{scriptVersion: scriptPubKey.Version, flags: flags, sigCache: sigCache, sigCacheECDSA: sigCacheECDSA}
|
||||
|
||||
if vm.scriptVersion > constants.MaxScriptPublicKeyVersion {
|
||||
return &vm, nil
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestBadPC(t *testing.T) {
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("NOP", 0), Version: 0}
|
||||
|
||||
for _, test := range tests {
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create script: %v", err)
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func TestCheckErrorCondition(t *testing.T) {
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm(test.script, 0), Version: 0}
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("TestCheckErrorCondition: %d: failed to create script: %v", i, err)
|
||||
}
|
||||
@@ -252,7 +252,7 @@ func TestDisasmPC(t *testing.T) {
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("OP_DROP NOP TRUE", 0), Version: 0}
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create script: %v", err)
|
||||
}
|
||||
@@ -315,7 +315,7 @@ func TestDisasmScript(t *testing.T) {
|
||||
}
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("OP_DROP NOP TRUE", 0), Version: 0}
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create script: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func ExamplePayToAddrScript() {
|
||||
// which is useful to ensure the accuracy of the address and determine
|
||||
// the address type. It is also required for the upcoming call to
|
||||
// PayToAddrScript.
|
||||
addressStr := "kaspa:qqfgqp8l9l90zwetj84k2jcac2m8falvvyy8xjtnhdac2m8falvvyvc9fuvqt"
|
||||
addressStr := "kaspa:qqj9fg59mptxkr9j0y53j5mwurcmda5mtza9n6v9pm9uj8h0wgk6uma5pvumr"
|
||||
address, err := util.DecodeAddress(addressStr, util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -45,15 +45,15 @@ func ExamplePayToAddrScript() {
|
||||
fmt.Println("Script Disassembly:", disasm)
|
||||
|
||||
// Output:
|
||||
// Script Hex: 76aa20128004ff2fcaf13b2b91eb654b1dc2b674f7ec6108734973bb7b856ce9efd8c288ac
|
||||
// Script Disassembly: OP_DUP OP_BLAKE2B 128004ff2fcaf13b2b91eb654b1dc2b674f7ec6108734973bb7b856ce9efd8c2 OP_EQUALVERIFY OP_CHECKSIG
|
||||
// Script Hex: 202454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeac
|
||||
// Script Disassembly: 2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722dae OP_CHECKSIG
|
||||
}
|
||||
|
||||
// This example demonstrates extracting information from a standard public key
|
||||
// script.
|
||||
func ExampleExtractScriptPubKeyAddress() {
|
||||
// Start with a standard pay-to-pubkey-hash script.
|
||||
scriptHex := "76aa20128004ff2fcaf13b2b91eb65128004ff2fcaf13b2b91eb654b1dc2b674f7ec6188ac"
|
||||
// Start with a standard pay-to-pubkey script.
|
||||
scriptHex := "2089ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555eac"
|
||||
script, err := hex.DecodeString(scriptHex)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -74,6 +74,6 @@ func ExampleExtractScriptPubKeyAddress() {
|
||||
fmt.Println("Address:", address)
|
||||
|
||||
// Output:
|
||||
// Script Class: pubkeyhash
|
||||
// Address: kaspa:qqfgqp8l9l90zwetj84k2y5qqnljljh38v4er6m9fvwu9dn57lkxztu8quj75
|
||||
// Script Class: pubkey
|
||||
// Address: kaspa:qzy6cf82zzah2xh5jwtz8nx9u4gdj6zzke8gljs0v055ksmnl424u6fv7ajrs
|
||||
}
|
||||
|
||||
@@ -204,9 +204,9 @@ const (
|
||||
OpUnknown166 = 0xa6 // 166
|
||||
OpUnknown167 = 0xa7 // 167
|
||||
OpSHA256 = 0xa8 // 168
|
||||
OpUnknown169 = 0xa9 // 169
|
||||
OpCheckMultiSigECDSA = 0xa9 // 169
|
||||
OpBlake2b = 0xaa // 170
|
||||
OpUnknown171 = 0xab // 171
|
||||
OpCheckSigECDSA = 0xab // 171
|
||||
OpCheckSig = 0xac // 172
|
||||
OpCheckSigVerify = 0xad // 173
|
||||
OpCheckMultiSig = 0xae // 174
|
||||
@@ -485,8 +485,10 @@ var opcodeArray = [256]opcode{
|
||||
OpWithin: {OpWithin, "OP_WITHIN", 1, opcodeWithin},
|
||||
|
||||
// Crypto opcodes.
|
||||
OpCheckMultiSigECDSA: {OpCheckMultiSigECDSA, "OP_CHECKMULTISIGECDSA", 1, opcodeCheckMultiSigECDSA},
|
||||
OpSHA256: {OpSHA256, "OP_SHA256", 1, opcodeSha256},
|
||||
OpBlake2b: {OpBlake2b, "OP_BLAKE2B", 1, opcodeBlake2b},
|
||||
OpCheckSigECDSA: {OpCheckSigECDSA, "OP_CHECKSIGECDSA", 1, opcodeCheckSigECDSA},
|
||||
OpCheckSig: {OpCheckSig, "OP_CHECKSIG", 1, opcodeCheckSig},
|
||||
OpCheckSigVerify: {OpCheckSigVerify, "OP_CHECKSIGVERIFY", 1, opcodeCheckSigVerify},
|
||||
OpCheckMultiSig: {OpCheckMultiSig, "OP_CHECKMULTISIG", 1, opcodeCheckMultiSig},
|
||||
@@ -507,8 +509,6 @@ var opcodeArray = [256]opcode{
|
||||
// Undefined opcodes.
|
||||
OpUnknown166: {OpUnknown166, "OP_UNKNOWN166", 1, opcodeInvalid},
|
||||
OpUnknown167: {OpUnknown167, "OP_UNKNOWN167", 1, opcodeInvalid},
|
||||
OpUnknown169: {OpUnknown169, "OP_UNKNOWN169", 1, opcodeInvalid},
|
||||
OpUnknown171: {OpUnknown171, "OP_UNKNOWN171", 1, opcodeInvalid},
|
||||
OpUnknown188: {OpUnknown188, "OP_UNKNOWN188", 1, opcodeInvalid},
|
||||
OpUnknown189: {OpUnknown189, "OP_UNKNOWN189", 1, opcodeInvalid},
|
||||
OpUnknown190: {OpUnknown190, "OP_UNKNOWN190", 1, opcodeInvalid},
|
||||
@@ -1988,7 +1988,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHash(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
sigHash, err := consensushashing.CalculateSignatureHashSchnorr(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
@@ -2027,6 +2027,89 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func opcodeCheckSigECDSA(op *parsedOpcode, vm *Engine) error {
|
||||
pkBytes, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullSigBytes, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The signature actually needs needs to be longer than this, but at
|
||||
// least 1 byte is needed for the hash type below. The full length is
|
||||
// checked depending on the script flags and upon parsing the signature.
|
||||
if len(fullSigBytes) < 1 {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Trim off hashtype from the signature string and check if the
|
||||
// signature and pubkey conform to the strict encoding requirements
|
||||
// depending on the flags.
|
||||
//
|
||||
// NOTE: When the strict encoding flags are set, any errors in the
|
||||
// signature or public encoding here result in an immediate script error
|
||||
// (and thus no result bool is pushed to the data stack). This differs
|
||||
// from the logic below where any errors in parsing the signature is
|
||||
// treated as the signature failure resulting in false being pushed to
|
||||
// the data stack. This is required because the more general script
|
||||
// validation consensus rules do not have the new strict encoding
|
||||
// requirements enabled by the flags.
|
||||
hashType := consensushashing.SigHashType(fullSigBytes[len(fullSigBytes)-1])
|
||||
sigBytes := fullSigBytes[:len(fullSigBytes)-1]
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
return scriptError(ErrInvalidSigHashType, fmt.Sprintf("invalid hash type 0x%x", hashType))
|
||||
}
|
||||
if err := vm.checkSignatureLengthECDSA(sigBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := vm.checkPubKeyEncodingECDSA(pkBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHashECDSA(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
pubKey, err := secp256k1.DeserializeECDSAPubKey(pkBytes)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
signature, err := secp256k1.DeserializeECDSASignatureFromSlice(sigBytes)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
var valid bool
|
||||
secpHash := secp256k1.Hash(*sigHash.ByteArray())
|
||||
if vm.sigCacheECDSA != nil {
|
||||
|
||||
valid = vm.sigCacheECDSA.Exists(secpHash, signature, pubKey)
|
||||
if !valid && pubKey.ECDSAVerify(&secpHash, signature) {
|
||||
vm.sigCacheECDSA.Add(secpHash, signature, pubKey)
|
||||
valid = true
|
||||
}
|
||||
} else {
|
||||
valid = pubKey.ECDSAVerify(&secpHash, signature)
|
||||
}
|
||||
|
||||
if !valid && len(sigBytes) > 0 {
|
||||
str := "signature not empty on failed checksig"
|
||||
return scriptError(ErrNullFail, str)
|
||||
}
|
||||
|
||||
vm.dstack.PushBool(valid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodeCheckSigVerify is a combination of opcodeCheckSig and opcodeVerify.
|
||||
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
|
||||
// documentation for each of those opcodes for more details.
|
||||
@@ -2049,6 +2132,12 @@ type parsedSigInfo struct {
|
||||
parsed bool
|
||||
}
|
||||
|
||||
type parsedSigInfoECDSA struct {
|
||||
signature []byte
|
||||
parsedSignature *secp256k1.ECDSASignature
|
||||
parsed bool
|
||||
}
|
||||
|
||||
// opcodeCheckMultiSig treats the top item on the stack as an integer number of
|
||||
// public keys, followed by that many entries as raw data representing the public
|
||||
// keys, followed by the integer number of signatures, followed by that many
|
||||
@@ -2194,7 +2283,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHash(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
sigHash, err := consensushashing.CalculateSignatureHashSchnorr(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2231,6 +2320,175 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func opcodeCheckMultiSigECDSA(op *parsedOpcode, vm *Engine) error {
|
||||
numKeys, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
numPubKeys := int(numKeys.Int32())
|
||||
if numPubKeys < 0 {
|
||||
str := fmt.Sprintf("number of pubkeys %d is negative",
|
||||
numPubKeys)
|
||||
return scriptError(ErrInvalidPubKeyCount, str)
|
||||
}
|
||||
if numPubKeys > MaxPubKeysPerMultiSig {
|
||||
str := fmt.Sprintf("too many pubkeys: %d > %d",
|
||||
numPubKeys, MaxPubKeysPerMultiSig)
|
||||
return scriptError(ErrInvalidPubKeyCount, str)
|
||||
}
|
||||
vm.numOps += numPubKeys
|
||||
if vm.numOps > MaxOpsPerScript {
|
||||
str := fmt.Sprintf("exceeded max operation limit of %d",
|
||||
MaxOpsPerScript)
|
||||
return scriptError(ErrTooManyOperations, str)
|
||||
}
|
||||
|
||||
pubKeys := make([][]byte, 0, numPubKeys)
|
||||
for i := 0; i < numPubKeys; i++ {
|
||||
pubKey, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKeys = append(pubKeys, pubKey)
|
||||
}
|
||||
|
||||
numSigs, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numSignatures := int(numSigs.Int32())
|
||||
if numSignatures < 0 {
|
||||
str := fmt.Sprintf("number of signatures %d is negative",
|
||||
numSignatures)
|
||||
return scriptError(ErrInvalidSignatureCount, str)
|
||||
|
||||
}
|
||||
if numSignatures > numPubKeys {
|
||||
str := fmt.Sprintf("more signatures than pubkeys: %d > %d",
|
||||
numSignatures, numPubKeys)
|
||||
return scriptError(ErrInvalidSignatureCount, str)
|
||||
}
|
||||
|
||||
signatures := make([]*parsedSigInfoECDSA, 0, numSignatures)
|
||||
for i := 0; i < numSignatures; i++ {
|
||||
signature, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigInfo := &parsedSigInfoECDSA{signature: signature}
|
||||
signatures = append(signatures, sigInfo)
|
||||
}
|
||||
|
||||
success := true
|
||||
numPubKeys++
|
||||
pubKeyIdx := -1
|
||||
signatureIdx := 0
|
||||
|
||||
for numSignatures > 0 {
|
||||
// When there are more signatures than public keys remaining,
|
||||
// there is no way to succeed since too many signatures are
|
||||
// invalid, so exit early.
|
||||
pubKeyIdx++
|
||||
numPubKeys--
|
||||
if numSignatures > numPubKeys {
|
||||
success = false
|
||||
break
|
||||
}
|
||||
|
||||
sigInfo := signatures[signatureIdx]
|
||||
pubKey := pubKeys[pubKeyIdx]
|
||||
|
||||
// The order of the signature and public key evaluation is
|
||||
// important here since it can be distinguished by an
|
||||
// OP_CHECKMULTISIG NOT when the strict encoding flag is set.
|
||||
|
||||
rawSig := sigInfo.signature
|
||||
if len(rawSig) == 0 {
|
||||
// Skip to the next pubkey if signature is empty.
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the signature into hash type and signature components.
|
||||
hashType := consensushashing.SigHashType(rawSig[len(rawSig)-1])
|
||||
signature := rawSig[:len(rawSig)-1]
|
||||
|
||||
// Only parse and check the signature encoding once.
|
||||
var parsedSig *secp256k1.ECDSASignature
|
||||
if !sigInfo.parsed {
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
return scriptError(ErrInvalidSigHashType, fmt.Sprintf("invalid hash type 0x%x", hashType))
|
||||
}
|
||||
if err := vm.checkSignatureLengthECDSA(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the signature.
|
||||
parsedSig, err = secp256k1.DeserializeECDSASignatureFromSlice(signature)
|
||||
sigInfo.parsed = true
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sigInfo.parsedSignature = parsedSig
|
||||
} else {
|
||||
// Skip to the next pubkey if the signature is invalid.
|
||||
if sigInfo.parsedSignature == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the already parsed signature.
|
||||
parsedSig = sigInfo.parsedSignature
|
||||
}
|
||||
|
||||
if err := vm.checkPubKeyEncodingECDSA(pubKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the pubkey.
|
||||
parsedPubKey, err := secp256k1.DeserializeECDSAPubKey(pubKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
sigHash, err := consensushashing.CalculateSignatureHashECDSA(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secpHash := secp256k1.Hash(*sigHash.ByteArray())
|
||||
var valid bool
|
||||
if vm.sigCacheECDSA != nil {
|
||||
valid = vm.sigCacheECDSA.Exists(secpHash, parsedSig, parsedPubKey)
|
||||
if !valid && parsedPubKey.ECDSAVerify(&secpHash, parsedSig) {
|
||||
vm.sigCacheECDSA.Add(secpHash, parsedSig, parsedPubKey)
|
||||
valid = true
|
||||
}
|
||||
} else {
|
||||
valid = parsedPubKey.ECDSAVerify(&secpHash, parsedSig)
|
||||
}
|
||||
|
||||
if valid {
|
||||
// PubKey verified, move on to the next signature.
|
||||
signatureIdx++
|
||||
numSignatures--
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
for _, sig := range signatures {
|
||||
if len(sig.signature) > 0 {
|
||||
str := "not all signatures empty on failed checkmultisig"
|
||||
return scriptError(ErrNullFail, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.dstack.PushBool(success)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodeCheckMultiSigVerify is a combination of opcodeCheckMultiSig and
|
||||
// opcodeVerify. The opcodeCheckMultiSig is invoked followed by opcodeVerify.
|
||||
// See the documentation for each of those opcodes for more details.
|
||||
|
||||
@@ -71,9 +71,9 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||
0x9f: "OP_LESSTHAN", 0xa0: "OP_GREATERTHAN",
|
||||
0xa1: "OP_LESSTHANOREQUAL", 0xa2: "OP_GREATERTHANOREQUAL",
|
||||
0xa3: "OP_MIN", 0xa4: "OP_MAX", 0xa5: "OP_WITHIN",
|
||||
0xa8: "OP_SHA256",
|
||||
0xa8: "OP_SHA256", 0xa9: "OP_CHECKMULTISIGECDSA",
|
||||
0xaa: "OP_BLAKE2B",
|
||||
0xac: "OP_CHECKSIG", 0xad: "OP_CHECKSIGVERIFY",
|
||||
0xab: "OP_CHECKSIGECDSA", 0xac: "OP_CHECKSIG", 0xad: "OP_CHECKSIGVERIFY",
|
||||
0xae: "OP_CHECKMULTISIG", 0xaf: "OP_CHECKMULTISIGVERIFY",
|
||||
0xb0: "OP_CHECKLOCKTIMEVERIFY", 0xb1: "OP_CHECKSEQUENCEVERIFY",
|
||||
0xfa: "OP_SMALLINTEGER", 0xfb: "OP_PUBKEYS",
|
||||
@@ -186,8 +186,8 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||
}
|
||||
|
||||
func isOpUnknown(opcodeVal int) bool {
|
||||
return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc || opcodeVal == 0xab ||
|
||||
opcodeVal == 0xa6 || opcodeVal == 0xa7 || opcodeVal == 0xa9
|
||||
return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc ||
|
||||
opcodeVal == 0xa6 || opcodeVal == 0xa7
|
||||
}
|
||||
|
||||
func isNumberedNop(opcodeVal int) bool {
|
||||
|
||||
@@ -260,8 +260,10 @@ func createSpendingTx(sigScript []byte, scriptPubKey *externalapi.ScriptPublicKe
|
||||
func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// Create a signature cache to use only if requested.
|
||||
var sigCache *SigCache
|
||||
var sigCacheECDSA *SigCacheECDSA
|
||||
if useSigCache {
|
||||
sigCache = NewSigCache(10)
|
||||
sigCacheECDSA = NewSigCacheECDSA(10)
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
@@ -343,7 +345,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// used, then create a new engine to execute the scripts.
|
||||
tx := createSpendingTx(scriptSig, scriptPubKey)
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, sigCacheECDSA, &consensushashing.SighashReusedValues{})
|
||||
if err == nil {
|
||||
err = vm.Execute()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type sigCacheEntry struct {
|
||||
pubKey *secp256k1.SchnorrPublicKey
|
||||
}
|
||||
|
||||
// SigCache implements an ECDSA signature verification cache with a randomized
|
||||
// SigCache implements an Schnorr signature verification cache with a randomized
|
||||
// entry eviction policy. Only valid signatures will be added to the cache. The
|
||||
// benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS
|
||||
// attack wherein an attack causes a victim's client to hang due to worst-case
|
||||
|
||||
90
domain/consensus/utils/txscript/sigcache_ecdsa.go
Normal file
90
domain/consensus/utils/txscript/sigcache_ecdsa.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
)
|
||||
|
||||
// sigCacheEntryECDSA represents an entry in the SigCache. Entries within the
|
||||
// SigCache are keyed according to the sigHash of the signature. In the
|
||||
// scenario of a cache-hit (according to the sigHash), an additional comparison
|
||||
// of the signature, and public key will be executed in order to ensure a complete
|
||||
// match. In the occasion that two sigHashes collide, the newer sigHash will
|
||||
// simply overwrite the existing entry.
|
||||
type sigCacheEntryECDSA struct {
|
||||
sig *secp256k1.ECDSASignature
|
||||
pubKey *secp256k1.ECDSAPublicKey
|
||||
}
|
||||
|
||||
// SigCacheECDSA implements an ECDSA signature verification cache with a randomized
|
||||
// entry eviction policy. Only valid signatures will be added to the cache. The
|
||||
// benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS
|
||||
// attack wherein an attack causes a victim's client to hang due to worst-case
|
||||
// behavior triggered while processing attacker crafted invalid transactions. A
|
||||
// detailed description of the mitigated DoS attack can be found here:
|
||||
// https://bitslog.wordpress.com/2013/01/23/fixed-bitcoin-vulnerability-explanation-why-the-signature-cache-is-a-dos-protection/.
|
||||
// Secondly, usage of the SigCache introduces a signature verification
|
||||
// optimization which speeds up the validation of transactions within a block,
|
||||
// if they've already been seen and verified within the mempool.
|
||||
type SigCacheECDSA struct {
|
||||
validSigs map[secp256k1.Hash]sigCacheEntryECDSA
|
||||
maxEntries uint
|
||||
}
|
||||
|
||||
// NewSigCacheECDSA creates and initializes a new instance of SigCache. Its sole
|
||||
// parameter 'maxEntries' represents the maximum number of entries allowed to
|
||||
// exist in the SigCache at any particular moment. Random entries are evicted
|
||||
// to make room for new entries that would cause the number of entries in the
|
||||
// cache to exceed the max.
|
||||
func NewSigCacheECDSA(maxEntries uint) *SigCacheECDSA {
|
||||
return &SigCacheECDSA{
|
||||
validSigs: make(map[secp256k1.Hash]sigCacheEntryECDSA, maxEntries),
|
||||
maxEntries: maxEntries,
|
||||
}
|
||||
}
|
||||
|
||||
// Exists returns true if an existing entry of 'sig' over 'sigHash' for public
|
||||
// key 'pubKey' is found within the SigCache. Otherwise, false is returned.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Readers won't be blocked
|
||||
// unless there exists a writer, adding an entry to the SigCache.
|
||||
func (s *SigCacheECDSA) Exists(sigHash secp256k1.Hash, sig *secp256k1.ECDSASignature, pubKey *secp256k1.ECDSAPublicKey) bool {
|
||||
entry, ok := s.validSigs[sigHash]
|
||||
|
||||
return ok && entry.pubKey.IsEqual(pubKey) && entry.sig.IsEqual(sig)
|
||||
}
|
||||
|
||||
// Add adds an entry for a signature over 'sigHash' under public key 'pubKey'
|
||||
// to the signature cache. In the event that the SigCache is 'full', an
|
||||
// existing entry is randomly chosen to be evicted in order to make space for
|
||||
// the new entry.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Writers will block
|
||||
// simultaneous readers until function execution has concluded.
|
||||
func (s *SigCacheECDSA) Add(sigHash secp256k1.Hash, sig *secp256k1.ECDSASignature, pubKey *secp256k1.ECDSAPublicKey) {
|
||||
if s.maxEntries == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If adding this new entry will put us over the max number of allowed
|
||||
// entries, then evict an entry.
|
||||
if uint(len(s.validSigs)+1) > s.maxEntries {
|
||||
// Remove a random entry from the map. Relying on the random
|
||||
// starting point of Go's map iteration. It's worth noting that
|
||||
// the random iteration starting point is not 100% guaranteed
|
||||
// by the spec, however most Go compilers support it.
|
||||
// Ultimately, the iteration order isn't important here because
|
||||
// in order to manipulate which items are evicted, an adversary
|
||||
// would need to be able to execute preimage attacks on the
|
||||
// hashing function in order to start eviction at a specific
|
||||
// entry.
|
||||
for sigEntry := range s.validSigs {
|
||||
delete(s.validSigs, sigEntry)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.validSigs[sigHash] = sigCacheEntryECDSA{sig, pubKey}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func RawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
key *secp256k1.SchnorrKeyPair, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
hash, err := consensushashing.CalculateSignatureHash(tx, idx, hashType, sighashReusedValues)
|
||||
hash, err := consensushashing.CalculateSignatureHashSchnorr(tx, idx, hashType, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,8 +32,26 @@ func RawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType conse
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// RawTxInSignatureECDSA returns the serialized ECDSA signature for the input idx of
|
||||
// the given transaction, with hashType appended to it.
|
||||
func RawTxInSignatureECDSA(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
key *secp256k1.ECDSAPrivateKey, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
hash, err := consensushashing.CalculateSignatureHashECDSA(tx, idx, hashType, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secpHash := secp256k1.Hash(*hash.ByteArray())
|
||||
signature, err := key.ECDSASign(&secpHash)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot sign tx input: %s", err)
|
||||
}
|
||||
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// SignatureScript creates an input signature script for tx to spend KAS sent
|
||||
// from a previous output to the owner of privKey. tx must include all
|
||||
// from a previous output to the owner of a Schnorr private key. tx must include all
|
||||
// transaction inputs and outputs, however txin scripts are allowed to be filled
|
||||
// or empty. The returned script is calculated to be used as the idx'th txin
|
||||
// sigscript for tx. script is the ScriptPublicKey of the previous output being used
|
||||
@@ -48,16 +66,26 @@ func SignatureScript(tx *externalapi.DomainTransaction, idx int, hashType consen
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk, err := privKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkData, err := pk.Serialize()
|
||||
return NewScriptBuilder().AddData(sig).Script()
|
||||
}
|
||||
|
||||
// SignatureScriptECDSA creates an input signature script for tx to spend KAS sent
|
||||
// from a previous output to the owner of an ECDSA private key. tx must include all
|
||||
// transaction inputs and outputs, however txin scripts are allowed to be filled
|
||||
// or empty. The returned script is calculated to be used as the idx'th txin
|
||||
// sigscript for tx. script is the ScriptPublicKey of the previous output being used
|
||||
// as the idx'th input. privKey is serialized in either a compressed or
|
||||
// uncompressed format based on compress. This format must match the same format
|
||||
// used to generate the payment address, or the script validation will fail.
|
||||
func SignatureScriptECDSA(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
privKey *secp256k1.ECDSAPrivateKey, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
sig, err := RawTxInSignatureECDSA(tx, idx, hashType, privKey, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewScriptBuilder().AddData(sig).AddData(pkData[:]).Script()
|
||||
return NewScriptBuilder().AddData(sig).Script()
|
||||
}
|
||||
|
||||
func sign(dagParams *dagconfig.Params, tx *externalapi.DomainTransaction, idx int,
|
||||
@@ -71,7 +99,7 @@ func sign(dagParams *dagconfig.Params, tx *externalapi.DomainTransaction, idx in
|
||||
}
|
||||
|
||||
switch class {
|
||||
case PubKeyHashTy:
|
||||
case PubKeyTy:
|
||||
// look up key for address
|
||||
key, err := kdb.GetKey(address)
|
||||
if err != nil {
|
||||
|
||||
@@ -54,7 +54,7 @@ func checkScripts(msg string, tx *externalapi.DomainTransaction, idx int, sigScr
|
||||
tx.Inputs[idx].SignatureScript = sigScript
|
||||
var flags ScriptFlags
|
||||
vm, err := NewEngine(scriptPubKey, tx, idx,
|
||||
flags, nil, &consensushashing.SighashReusedValues{})
|
||||
flags, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to make script engine for %s: %v",
|
||||
msg, err)
|
||||
@@ -143,7 +143,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Pay to Pubkey Hash (merging with correct)
|
||||
// Pay to Pubkey (merging with correct)
|
||||
for _, hashType := range hashTypes {
|
||||
for _, input := range tx.Inputs {
|
||||
input.UTXOEntry = utxo.NewUTXOEntry(500, scriptPubKey, false, 100)
|
||||
@@ -180,14 +180,14 @@ func TestSignTxOutput(t *testing.T) {
|
||||
|
||||
err = checkScripts(msg, tx, i, sigScript, scriptPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("twice signed script invalid for "+
|
||||
t.Fatalf("twice signed script invalid for "+
|
||||
"%s: %v", msg, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pay to Pubkey Hash (compressed)
|
||||
// Pay to Pubkey
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
@@ -213,8 +213,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -238,7 +237,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Pay to Pubkey Hash with duplicate merge
|
||||
// Pay to Pubkey with duplicate merge
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
@@ -264,8 +263,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -316,7 +314,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
|
||||
// As before, but with p2sh now.
|
||||
|
||||
// Pay to Pubkey Hash
|
||||
// Pay to Pubkey
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
@@ -342,8 +340,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -381,7 +378,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Pay to Pubkey Hash (compressed) with duplicate merge
|
||||
// Pay to Pubkey with duplicate merge
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.Inputs {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
@@ -407,8 +404,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -474,7 +470,7 @@ func TestSignTxOutput(t *testing.T) {
|
||||
}
|
||||
|
||||
func generateKeys() (keyPair *secp256k1.SchnorrKeyPair, scriptPublicKey *externalapi.ScriptPublicKey,
|
||||
addressPubKeyHash *util.AddressPubKeyHash, err error) {
|
||||
addressPubKeyHash *util.AddressPublicKey, err error) {
|
||||
|
||||
key, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
@@ -490,8 +486,7 @@ func generateKeys() (keyPair *secp256k1.SchnorrKeyPair, scriptPublicKey *externa
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Errorf("failed to serialize a pubkey for %s: %s", pubKey, err)
|
||||
}
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.HashBlake2b(serializedPubKey[:]), util.Bech32PrefixKaspaTest)
|
||||
address, err := util.NewAddressPublicKey(serializedPubKey[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Errorf("failed to make address for %s: %s", serializedPubKey, err)
|
||||
}
|
||||
@@ -534,12 +529,10 @@ var (
|
||||
oldCompressedScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x76, 0xa9, 0x14, 0x27, 0x4d, 0x9f, 0x7f,
|
||||
0x61, 0x7e, 0x7c, 0x7a, 0x1c, 0x1f, 0xb2, 0x75, 0x79, 0x10,
|
||||
0x43, 0x65, 0x68, 0x27, 0x9d, 0x86, 0x88, 0xac}, 0}
|
||||
p2pkhScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x76, 0xaa, 0x20,
|
||||
0x51, 0x9c, 0x25, 0xca, 0x95, 0xa0, 0xd8, 0xcd,
|
||||
0xf5, 0xb8, 0x3f, 0x96, 0xa1, 0x5e, 0x8c, 0x1a,
|
||||
0xae, 0x33, 0xeb, 0x50, 0xc8, 0x66, 0xc9, 0xd0,
|
||||
0xa5, 0xce, 0x3e, 0x5f, 0x6b, 0x3b, 0x38, 0x8d,
|
||||
0x88, 0xac}, 0}
|
||||
p2pkScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x20, 0xb2, 0x52, 0xf0, 0x49, 0x85, 0x78, 0x03, 0x03,
|
||||
0xc8, 0x7d, 0xce, 0x51, 0x7f, 0xa8, 0x69, 0x0b,
|
||||
0x91, 0x95, 0xf4, 0xf3, 0x5c, 0x26, 0x73, 0x05,
|
||||
0x05, 0xa2, 0xee, 0xbc, 0x09, 0x38, 0x34, 0x3a, 0xac}, 0}
|
||||
shortScriptPubKey = &externalapi.ScriptPublicKey{[]byte{0x76, 0xa9, 0x14, 0xd1, 0x7c, 0xb5,
|
||||
0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32,
|
||||
0x53, 0x90, 0x0e, 0x0a, 0x88, 0xac}, 0}
|
||||
@@ -638,7 +631,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -654,7 +647,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -663,7 +656,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal + fee,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -679,7 +672,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -695,7 +688,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -711,7 +704,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -727,7 +720,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: false,
|
||||
inputValidates: false,
|
||||
@@ -743,7 +736,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: false,
|
||||
inputValidates: false,
|
||||
@@ -759,7 +752,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -768,7 +761,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal + fee,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -784,7 +777,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -793,7 +786,7 @@ var sigScriptTests = []tstSigScript{
|
||||
{
|
||||
txout: &externalapi.DomainTransactionOutput{
|
||||
Value: coinbaseVal + fee,
|
||||
ScriptPublicKey: p2pkhScriptPubKey,
|
||||
ScriptPublicKey: p2pkScriptPubKey,
|
||||
},
|
||||
sigscriptGenerates: true,
|
||||
inputValidates: true,
|
||||
@@ -877,7 +870,7 @@ nexttest:
|
||||
// Validate tx input scripts
|
||||
var scriptFlags ScriptFlags
|
||||
for j := range tx.Inputs {
|
||||
vm, err := NewEngine(sigScriptTests[i].inputs[j].txout.ScriptPublicKey, tx, j, scriptFlags, nil,
|
||||
vm, err := NewEngine(sigScriptTests[i].inputs[j].txout.ScriptPublicKey, tx, j, scriptFlags, nil, nil,
|
||||
&consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("cannot create script vm for test %v: %v",
|
||||
|
||||
@@ -21,15 +21,24 @@ type ScriptClass byte
|
||||
// Classes of script payment known about in the blockDAG.
|
||||
const (
|
||||
NonStandardTy ScriptClass = iota // None of the recognized forms.
|
||||
PubKeyHashTy // Pay pubkey hash.
|
||||
PubKeyTy // Pay to pubkey.
|
||||
PubKeyECDSATy // Pay to pubkey ECDSA.
|
||||
ScriptHashTy // Pay to script hash.
|
||||
)
|
||||
|
||||
// Script public key versions for address types.
|
||||
const (
|
||||
addressPublicKeyScriptPublicKeyVersion = 0
|
||||
addressPublicKeyECDSAScriptPublicKeyVersion = 0
|
||||
addressScriptHashScriptPublicKeyVersion = 0
|
||||
)
|
||||
|
||||
// scriptClassToName houses the human-readable strings which describe each
|
||||
// script class.
|
||||
var scriptClassToName = []string{
|
||||
NonStandardTy: "nonstandard",
|
||||
PubKeyHashTy: "pubkeyhash",
|
||||
PubKeyTy: "pubkey",
|
||||
PubKeyECDSATy: "pubkeyecdsa",
|
||||
ScriptHashTy: "scripthash",
|
||||
}
|
||||
|
||||
@@ -43,24 +52,32 @@ func (t ScriptClass) String() string {
|
||||
return scriptClassToName[t]
|
||||
}
|
||||
|
||||
// isPubkeyHash returns true if the script passed is a pay-to-pubkey-hash
|
||||
// isPayToPubkey returns true if the script passed is a pay-to-pubkey
|
||||
// transaction, false otherwise.
|
||||
func isPubkeyHash(pops []parsedOpcode) bool {
|
||||
return len(pops) == 5 &&
|
||||
pops[0].opcode.value == OpDup &&
|
||||
pops[1].opcode.value == OpBlake2b &&
|
||||
pops[2].opcode.value == OpData32 &&
|
||||
pops[3].opcode.value == OpEqualVerify &&
|
||||
pops[4].opcode.value == OpCheckSig
|
||||
func isPayToPubkey(pops []parsedOpcode) bool {
|
||||
return len(pops) == 2 &&
|
||||
pops[0].opcode.value == OpData32 &&
|
||||
pops[1].opcode.value == OpCheckSig
|
||||
}
|
||||
|
||||
// isPayToPubkeyECDSA returns true if the script passed is an ECDSA pay-to-pubkey
|
||||
// transaction, false otherwise.
|
||||
func isPayToPubkeyECDSA(pops []parsedOpcode) bool {
|
||||
return len(pops) == 2 &&
|
||||
pops[0].opcode.value == OpData33 &&
|
||||
pops[1].opcode.value == OpCheckSigECDSA
|
||||
|
||||
}
|
||||
|
||||
// scriptType returns the type of the script being inspected from the known
|
||||
// standard types.
|
||||
func typeOfScript(pops []parsedOpcode) ScriptClass {
|
||||
if isPubkeyHash(pops) {
|
||||
return PubKeyHashTy
|
||||
} else if isScriptHash(pops) {
|
||||
switch {
|
||||
case isPayToPubkey(pops):
|
||||
return PubKeyTy
|
||||
case isPayToPubkeyECDSA(pops):
|
||||
return PubKeyECDSATy
|
||||
case isScriptHash(pops):
|
||||
return ScriptHashTy
|
||||
}
|
||||
return NonStandardTy
|
||||
@@ -85,8 +102,8 @@ func GetScriptClass(script []byte) ScriptClass {
|
||||
func expectedInputs(pops []parsedOpcode, class ScriptClass) int {
|
||||
switch class {
|
||||
|
||||
case PubKeyHashTy:
|
||||
return 2
|
||||
case PubKeyTy:
|
||||
return 1
|
||||
|
||||
case ScriptHashTy:
|
||||
// Not including script. That is handled by the caller.
|
||||
@@ -169,12 +186,21 @@ func CalcScriptInfo(sigScript, scriptPubKey []byte, isP2SH bool) (*ScriptInfo, e
|
||||
return si, nil
|
||||
}
|
||||
|
||||
// payToPubKeyHashScript creates a new script to pay a transaction
|
||||
// output to a 20-byte pubkey hash. It is expected that the input is a valid
|
||||
// hash.
|
||||
func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().AddOp(OpDup).AddOp(OpBlake2b).
|
||||
AddData(pubKeyHash).AddOp(OpEqualVerify).AddOp(OpCheckSig).
|
||||
// payToPubKeyScript creates a new script to pay a transaction
|
||||
// output to a 32-byte pubkey.
|
||||
func payToPubKeyScript(pubKey []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().
|
||||
AddData(pubKey).
|
||||
AddOp(OpCheckSig).
|
||||
Script()
|
||||
}
|
||||
|
||||
// payToPubKeyScript creates a new script to pay a transaction
|
||||
// output to a 33-byte pubkey.
|
||||
func payToPubKeyScriptECDSA(pubKey []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().
|
||||
AddData(pubKey).
|
||||
AddOp(OpCheckSigECDSA).
|
||||
Script()
|
||||
}
|
||||
|
||||
@@ -190,16 +216,29 @@ func payToScriptHashScript(scriptHash []byte) ([]byte, error) {
|
||||
func PayToAddrScript(addr util.Address) (*externalapi.ScriptPublicKey, error) {
|
||||
const nilAddrErrStr = "unable to generate payment script for nil address"
|
||||
switch addr := addr.(type) {
|
||||
case *util.AddressPubKeyHash:
|
||||
case *util.AddressPublicKey:
|
||||
if addr == nil {
|
||||
return nil, scriptError(ErrUnsupportedAddress,
|
||||
nilAddrErrStr)
|
||||
}
|
||||
script, err := payToPubKeyHashScript(addr.ScriptAddress())
|
||||
script, err := payToPubKeyScript(addr.ScriptAddress())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &externalapi.ScriptPublicKey{script, constants.MaxScriptPublicKeyVersion}, err
|
||||
|
||||
return &externalapi.ScriptPublicKey{script, addressPublicKeyScriptPublicKeyVersion}, err
|
||||
|
||||
case *util.AddressPublicKeyECDSA:
|
||||
if addr == nil {
|
||||
return nil, scriptError(ErrUnsupportedAddress,
|
||||
nilAddrErrStr)
|
||||
}
|
||||
script, err := payToPubKeyScriptECDSA(addr.ScriptAddress())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &externalapi.ScriptPublicKey{script, addressPublicKeyECDSAScriptPublicKeyVersion}, err
|
||||
|
||||
case *util.AddressScriptHash:
|
||||
if addr == nil {
|
||||
@@ -210,7 +249,8 @@ func PayToAddrScript(addr util.Address) (*externalapi.ScriptPublicKey, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &externalapi.ScriptPublicKey{script, constants.MaxScriptPublicKeyVersion}, err
|
||||
|
||||
return &externalapi.ScriptPublicKey{script, addressScriptHashScriptPublicKeyVersion}, err
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("unable to generate payment script for unsupported "+
|
||||
@@ -276,12 +316,24 @@ func ExtractScriptPubKeyAddress(scriptPubKey *externalapi.ScriptPublicKey, dagPa
|
||||
|
||||
scriptClass := typeOfScript(pops)
|
||||
switch scriptClass {
|
||||
case PubKeyHashTy:
|
||||
// A pay-to-pubkey-hash script is of the form:
|
||||
// OP_DUP OP_BLAKE2B <hash> OP_EQUALVERIFY OP_CHECKSIG
|
||||
// Therefore the pubkey hash is the 3rd item on the stack.
|
||||
// If the pubkey hash is invalid for some reason, return a nil address.
|
||||
addr, err := util.NewAddressPubKeyHash(pops[2].data,
|
||||
case PubKeyTy:
|
||||
// A pay-to-pubkey script is of the form:
|
||||
// <pubkey> OP_CHECKSIG
|
||||
// Therefore the pubkey is the first item on the stack.
|
||||
// If the pubkey is invalid for some reason, return a nil address.
|
||||
addr, err := util.NewAddressPublicKey(pops[0].data,
|
||||
dagParams.Prefix)
|
||||
if err != nil {
|
||||
return scriptClass, nil, nil
|
||||
}
|
||||
return scriptClass, addr, nil
|
||||
|
||||
case PubKeyECDSATy:
|
||||
// A pay-to-pubkey script is of the form:
|
||||
// <pubkey> OP_CHECKSIGECDSA
|
||||
// Therefore the pubkey is the first item on the stack.
|
||||
// If the pubkey is invalid for some reason, return a nil address.
|
||||
addr, err := util.NewAddressPublicKeyECDSA(pops[0].data,
|
||||
dagParams.Prefix)
|
||||
if err != nil {
|
||||
return scriptClass, nil, nil
|
||||
|
||||
@@ -28,14 +28,27 @@ func mustParseShortForm(script string, version uint16) []byte {
|
||||
return s
|
||||
}
|
||||
|
||||
// newAddressPubKeyHash returns a new util.AddressPubKeyHash from the
|
||||
// provided hash. It panics if an error occurs. This is only used in the tests
|
||||
// newAddressPublicKey returns a new util.AddressPublicKey from the
|
||||
// provided public key. It panics if an error occurs. This is only used in the tests
|
||||
// as a helper since the only way it can fail is if there is an error in the
|
||||
// test source code.
|
||||
func newAddressPubKeyHash(pkHash []byte) util.Address {
|
||||
addr, err := util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspa)
|
||||
func newAddressPublicKey(publicKey []byte) util.Address {
|
||||
addr, err := util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
panic("invalid public key hash in test source")
|
||||
panic("invalid public key in test source")
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// newAddressPublicKeyECDSA returns a new util.AddressPublicKeyECDSA from the
|
||||
// provided public key. It panics if an error occurs. This is only used in the tests
|
||||
// as a helper since the only way it can fail is if there is an error in the
|
||||
// test source code.
|
||||
func newAddressPublicKeyECDSA(publicKey []byte) util.Address {
|
||||
addr, err := util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
panic("invalid public key in test source")
|
||||
}
|
||||
|
||||
return addr
|
||||
@@ -67,15 +80,22 @@ func TestExtractScriptPubKeyAddrs(t *testing.T) {
|
||||
class ScriptClass
|
||||
}{
|
||||
{
|
||||
name: "standard p2pkh",
|
||||
name: "standard p2pk",
|
||||
script: &externalapi.ScriptPublicKey{
|
||||
Script: hexToBytes("76aa20ad06dd6ddee55cbca9a9e3713bd" +
|
||||
"7587509a30564ad06dd6ddee55cbca9a9e37188ac"),
|
||||
Script: hexToBytes("202454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeac"),
|
||||
Version: 0,
|
||||
},
|
||||
addr: newAddressPubKeyHash(hexToBytes("ad06dd6ddee5" +
|
||||
"5cbca9a9e3713bd7587509a30564ad06dd6ddee55cbca9a9e371")),
|
||||
class: PubKeyHashTy,
|
||||
addr: newAddressPublicKey(hexToBytes("2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722dae")),
|
||||
class: PubKeyTy,
|
||||
},
|
||||
{
|
||||
name: "standard p2pk ECDSA",
|
||||
script: &externalapi.ScriptPublicKey{
|
||||
Script: hexToBytes("212454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeaaab"),
|
||||
Version: 0,
|
||||
},
|
||||
addr: newAddressPublicKeyECDSA(hexToBytes("2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeaa")),
|
||||
class: PubKeyECDSATy,
|
||||
},
|
||||
{
|
||||
name: "standard p2sh",
|
||||
@@ -216,8 +236,8 @@ func TestCalcScriptInfo(t *testing.T) {
|
||||
{
|
||||
// Invented scripts, the hashes do not match
|
||||
name: "p2sh standard script",
|
||||
sigScript: "1 81 DATA_37 DUP BLAKE2B DATA_32 0x010203" +
|
||||
"0405060708090a0b0c0d0e0f1011121314fe441065b6532231de2fac56 EQUALVERIFY " +
|
||||
sigScript: "1 81 DATA_34 DATA_32 0x010203" +
|
||||
"0405060708090a0b0c0d0e0f1011121314fe441065b6532231de2fac56 " +
|
||||
"CHECKSIG",
|
||||
scriptPubKey: "BLAKE2B DATA_32 0xfe441065b6532231de2fac56" +
|
||||
"3152205ec4f59c74fe441065b6532231de2fac56 EQUAL",
|
||||
@@ -225,7 +245,7 @@ func TestCalcScriptInfo(t *testing.T) {
|
||||
scriptInfo: ScriptInfo{
|
||||
ScriptPubKeyClass: ScriptHashTy,
|
||||
NumInputs: 3,
|
||||
ExpectedInputs: 3, // nonstandard p2sh.
|
||||
ExpectedInputs: 2, // nonstandard p2sh.
|
||||
SigOps: 1,
|
||||
},
|
||||
},
|
||||
@@ -303,10 +323,10 @@ func (b *bogusAddress) Prefix() util.Bech32Prefix {
|
||||
func TestPayToAddrScript(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p2pkhMain, err := util.NewAddressPubKeyHash(hexToBytes("e34cce70c86"+
|
||||
p2pkMain, err := util.NewAddressPublicKey(hexToBytes("e34cce70c86"+
|
||||
"373273efcc54ce7d2a491bb4a0e84e34cce70c86373273efcc54c"), util.Bech32PrefixKaspa)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create public key hash address: %v", err)
|
||||
t.Fatalf("Unable to create public key address: %v", err)
|
||||
}
|
||||
|
||||
p2shMain, err := util.NewAddressScriptHashFromHash(hexToBytes("e8c300"+
|
||||
@@ -325,11 +345,11 @@ func TestPayToAddrScript(t *testing.T) {
|
||||
expectedVersion uint16
|
||||
err error
|
||||
}{
|
||||
// pay-to-pubkey-hash address on mainnet
|
||||
// pay-to-pubkey address on mainnet
|
||||
{
|
||||
p2pkhMain,
|
||||
"DUP BLAKE2B DATA_32 0xe34cce70c86373273efcc54ce7d2a4" +
|
||||
"91bb4a0e84e34cce70c86373273efcc54c EQUALVERIFY CHECKSIG",
|
||||
p2pkMain,
|
||||
"DATA_32 0xe34cce70c86373273efcc54ce7d2a4" +
|
||||
"91bb4a0e84e34cce70c86373273efcc54c CHECKSIG",
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
@@ -343,7 +363,7 @@ func TestPayToAddrScript(t *testing.T) {
|
||||
},
|
||||
|
||||
// Supported address types with nil pointers.
|
||||
{(*util.AddressPubKeyHash)(nil), "", 0, errUnsupportedAddress},
|
||||
{(*util.AddressPublicKey)(nil), "", 0, errUnsupportedAddress},
|
||||
{(*util.AddressScriptHash)(nil), "", 0, errUnsupportedAddress},
|
||||
|
||||
// Unsupported address type.
|
||||
@@ -392,17 +412,21 @@ var scriptClassTests = []struct {
|
||||
}{
|
||||
// p2pk
|
||||
{
|
||||
name: "Pay Pubkey",
|
||||
script: "DATA_65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e" +
|
||||
"97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e16" +
|
||||
"0bfa9b8b64f9d4c03f999b8643f656b412a3 CHECKSIG",
|
||||
class: NonStandardTy,
|
||||
name: "Pay Pubkey",
|
||||
script: "DATA_32 0x89ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555e CHECKSIG",
|
||||
class: PubKeyTy,
|
||||
},
|
||||
// p2pk ECDSA
|
||||
{
|
||||
name: "Pay Pubkey ECDSA",
|
||||
script: "DATA_33 0x89ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555eab CHECKSIGECDSA",
|
||||
class: PubKeyECDSATy,
|
||||
},
|
||||
{
|
||||
name: "Pay PubkeyHash",
|
||||
script: "DUP BLAKE2B DATA_32 0x660d4ef3a743e3e696ad990364e55543e3e696ad990364e555e555" +
|
||||
"c271ad504b EQUALVERIFY CHECKSIG",
|
||||
class: PubKeyHashTy,
|
||||
class: NonStandardTy,
|
||||
},
|
||||
// mutlisig
|
||||
{
|
||||
@@ -513,9 +537,14 @@ func TestStringifyClass(t *testing.T) {
|
||||
stringed: "nonstandard",
|
||||
},
|
||||
{
|
||||
name: "pubkeyhash",
|
||||
class: PubKeyHashTy,
|
||||
stringed: "pubkeyhash",
|
||||
name: "pubkey",
|
||||
class: PubKeyTy,
|
||||
stringed: "pubkey",
|
||||
},
|
||||
{
|
||||
name: "pubkeyecdsa",
|
||||
class: PubKeyECDSATy,
|
||||
stringed: "pubkeyecdsa",
|
||||
},
|
||||
{
|
||||
name: "scripthash",
|
||||
|
||||
@@ -38,8 +38,8 @@ func main() {
|
||||
// later...
|
||||
|
||||
// Create and print new payment address, specific to the active network.
|
||||
pubKeyHash := make([]byte, 20)
|
||||
addr, err := util.NewAddressPubKeyHash(pubKeyHash, dagParams)
|
||||
pubKey := make([]byte, 32)
|
||||
addr, err := util.NewAddressPubKey(pubKey, dagParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ variable (either directly, or hidden in a library call).
|
||||
// later...
|
||||
|
||||
// Create and print new payment address, specific to the active network.
|
||||
pubKeyHash := make([]byte, 20)
|
||||
addr, err := util.NewAddressPubKeyHash(pubKeyHash, dagParams)
|
||||
pubKey := make([]byte, 32)
|
||||
addr, err := util.NewAddressPubKey(pubKey, dagParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -127,43 +127,18 @@ func isDust(txOut *consensusexternalapi.DomainTransactionOutput, minRelayTxFee u
|
||||
// input script to redeem it. Since there is no input script
|
||||
// to redeem it yet, use the minimum size of a typical input script.
|
||||
//
|
||||
// Pay-to-pubkey-hash bytes breakdown:
|
||||
//
|
||||
// Output to hash (34 bytes):
|
||||
// 8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160,
|
||||
// 1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG]
|
||||
//
|
||||
// Input with compressed pubkey (148 bytes):
|
||||
// 36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig,
|
||||
// 1 OP_DATA_33, 33 compressed pubkey], 4 sequence
|
||||
//
|
||||
// Input with uncompressed pubkey (180 bytes):
|
||||
// 36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig,
|
||||
// 1 OP_DATA_65, 65 compressed pubkey], 4 sequence
|
||||
//
|
||||
// Pay-to-pubkey bytes breakdown:
|
||||
//
|
||||
// Output to compressed pubkey (44 bytes):
|
||||
// 8 value, 1 script len, 35 script [1 OP_DATA_33,
|
||||
// 33 compressed pubkey, 1 OP_CHECKSIG]
|
||||
// Output to pubkey (43 bytes):
|
||||
// 8 value, 1 script len, 34 script [1 OP_DATA_32,
|
||||
// 32 pubkey, 1 OP_CHECKSIG]
|
||||
//
|
||||
// Output to uncompressed pubkey (76 bytes):
|
||||
// 8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey,
|
||||
// 1 OP_CHECKSIG]
|
||||
// Input (105 bytes):
|
||||
// 36 prev outpoint, 1 script len, 64 script [1 OP_DATA_64,
|
||||
// 64 sig], 4 sequence
|
||||
//
|
||||
// Input (114 bytes):
|
||||
// 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72,
|
||||
// 72 sig], 4 sequence
|
||||
//
|
||||
// Theoretically this could examine the script type of the output script
|
||||
// and use a different size for the typical input script size for
|
||||
// pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns,
|
||||
// but the only combination which is less than the value chosen is
|
||||
// a pay-to-pubkey script with a compressed pubkey, which is not very
|
||||
// common.
|
||||
//
|
||||
// The most common scripts are pay-to-pubkey-hash, and as per the above
|
||||
// breakdown, the minimum size of a p2pkh input script is 148 bytes. So
|
||||
// The most common scripts are pay-to-pubkey, and as per the above
|
||||
// breakdown, the minimum size of a p2pk input script is 148 bytes. So
|
||||
// that figure is used.
|
||||
totalSize := estimatedsize.TransactionOutputEstimatedSerializedSize(txOut) + 148
|
||||
|
||||
@@ -172,7 +147,7 @@ func isDust(txOut *consensusexternalapi.DomainTransactionOutput, minRelayTxFee u
|
||||
// minFreeTxRelayFee is in sompi/KB, so multiply by 1000 to
|
||||
// convert to bytes.
|
||||
//
|
||||
// Using the typical values for a pay-to-pubkey-hash transaction from
|
||||
// Using the typical values for a pay-to-pubkey transaction from
|
||||
// the breakdown above and the default minimum free transaction relay
|
||||
// fee of 1000, this equates to values less than 546 sompi being
|
||||
// considered dust.
|
||||
|
||||
@@ -179,9 +179,9 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
addrHash := [32]byte{0x01}
|
||||
addr, err := util.NewAddressPubKeyHash(addrHash[:], util.Bech32PrefixKaspaTest)
|
||||
addr, err := util.NewAddressPublicKey(addrHash[:], util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err)
|
||||
t.Fatalf("NewAddressPublicKey: unexpected error: %v", err)
|
||||
}
|
||||
dummyScriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
@@ -200,7 +200,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
code RejectCode
|
||||
}{
|
||||
{
|
||||
name: "Typical pay-to-pubkey-hash transaction",
|
||||
name: "Typical pay-to-pubkey transaction",
|
||||
tx: consensusexternalapi.DomainTransaction{Version: 0, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||
height: 300000,
|
||||
isStandard: true,
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const maxAddresses = 4096
|
||||
|
||||
// addressRandomizer is the interface for the randomizer needed for the AddressManager.
|
||||
type addressRandomizer interface {
|
||||
RandomAddress(addresses []*appmessage.NetAddress) *appmessage.NetAddress
|
||||
@@ -27,6 +29,11 @@ type addressKey struct {
|
||||
address ipv6
|
||||
}
|
||||
|
||||
type address struct {
|
||||
netAddress *appmessage.NetAddress
|
||||
connectionFailedCount uint64
|
||||
}
|
||||
|
||||
type ipv6 [net.IPv6len]byte
|
||||
|
||||
func (i ipv6) equal(other ipv6) bool {
|
||||
@@ -45,17 +52,6 @@ func netAddressKey(netAddress *appmessage.NetAddress) addressKey {
|
||||
return key
|
||||
}
|
||||
|
||||
// netAddressKeys returns a key of the ip address to use it in maps.
|
||||
func netAddressesKeys(netAddresses []*appmessage.NetAddress) map[addressKey]bool {
|
||||
result := make(map[addressKey]bool, len(netAddresses))
|
||||
for _, netAddress := range netAddresses {
|
||||
key := netAddressKey(netAddress)
|
||||
result[key] = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AddressManager provides a concurrency safe address manager for caching potential
|
||||
// peers on the Kaspa network.
|
||||
type AddressManager struct {
|
||||
@@ -85,13 +81,42 @@ func New(cfg *Config, database database.Database) (*AddressManager, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (am *AddressManager) addAddressNoLock(address *appmessage.NetAddress) error {
|
||||
if !IsRoutable(address, am.cfg.AcceptUnroutable) {
|
||||
func (am *AddressManager) addAddressNoLock(netAddress *appmessage.NetAddress) error {
|
||||
if !IsRoutable(netAddress, am.cfg.AcceptUnroutable) {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := netAddressKey(netAddress)
|
||||
address := &address{netAddress: netAddress, connectionFailedCount: 0}
|
||||
err := am.store.add(key, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if am.store.notBannedCount() > maxAddresses {
|
||||
allAddresses := am.store.getAllNotBanned()
|
||||
|
||||
maxConnectionFailedCount := uint64(0)
|
||||
toRemove := allAddresses[0]
|
||||
for _, address := range allAddresses[1:] {
|
||||
if address.connectionFailedCount > maxConnectionFailedCount {
|
||||
maxConnectionFailedCount = address.connectionFailedCount
|
||||
toRemove = address
|
||||
}
|
||||
}
|
||||
|
||||
toRemoveKey := netAddressKey(toRemove.netAddress)
|
||||
err := am.store.remove(toRemoveKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AddressManager) removeAddressNoLock(address *appmessage.NetAddress) error {
|
||||
key := netAddressKey(address)
|
||||
return am.store.add(key, address)
|
||||
return am.store.remove(key)
|
||||
}
|
||||
|
||||
// AddAddress adds address to the address manager
|
||||
@@ -121,8 +146,37 @@ func (am *AddressManager) RemoveAddress(address *appmessage.NetAddress) error {
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
return am.removeAddressNoLock(address)
|
||||
}
|
||||
|
||||
// MarkConnectionFailure notifies the address manager that the given address
|
||||
// has failed to connect
|
||||
func (am *AddressManager) MarkConnectionFailure(address *appmessage.NetAddress) error {
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
key := netAddressKey(address)
|
||||
return am.store.remove(key)
|
||||
entry, ok := am.store.getNotBanned(key)
|
||||
if !ok {
|
||||
return errors.Errorf("address %s is not registered with the address manager", address.TCPAddress())
|
||||
}
|
||||
entry.connectionFailedCount = entry.connectionFailedCount + 1
|
||||
return am.store.updateNotBanned(key, entry)
|
||||
}
|
||||
|
||||
// MarkConnectionSuccess notifies the address manager that the given address
|
||||
// has successfully connected
|
||||
func (am *AddressManager) MarkConnectionSuccess(address *appmessage.NetAddress) error {
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
key := netAddressKey(address)
|
||||
entry, ok := am.store.getNotBanned(key)
|
||||
if !ok {
|
||||
return errors.Errorf("address %s is not registered with the address manager", address.TCPAddress())
|
||||
}
|
||||
entry.connectionFailedCount = 0
|
||||
return am.store.updateNotBanned(key, entry)
|
||||
}
|
||||
|
||||
// Addresses returns all addresses
|
||||
@@ -130,7 +184,7 @@ func (am *AddressManager) Addresses() []*appmessage.NetAddress {
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
return am.store.getAllNotBanned()
|
||||
return am.store.getAllNotBannedNetAddresses()
|
||||
}
|
||||
|
||||
// BannedAddresses returns all banned addresses
|
||||
@@ -138,7 +192,7 @@ func (am *AddressManager) BannedAddresses() []*appmessage.NetAddress {
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
return am.store.getAllBanned()
|
||||
return am.store.getAllBannedNetAddresses()
|
||||
}
|
||||
|
||||
// notBannedAddressesWithException returns all not banned addresses with excpetion
|
||||
@@ -146,7 +200,7 @@ func (am *AddressManager) notBannedAddressesWithException(exceptions []*appmessa
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
return am.store.getAllNotBannedWithout(exceptions)
|
||||
return am.store.getAllNotBannedNetAddressesWithout(exceptions)
|
||||
}
|
||||
|
||||
// RandomAddress returns a random address that isn't banned and isn't in exceptions
|
||||
@@ -174,7 +228,7 @@ func (am *AddressManager) Ban(addressToBan *appmessage.NetAddress) error {
|
||||
|
||||
keyToBan := netAddressKey(addressToBan)
|
||||
keysToDelete := make([]addressKey, 0)
|
||||
for _, address := range am.store.getAllNotBanned() {
|
||||
for _, address := range am.store.getAllNotBannedNetAddresses() {
|
||||
key := netAddressKey(address)
|
||||
if key.address.equal(keyToBan.address) {
|
||||
keysToDelete = append(keysToDelete, key)
|
||||
@@ -187,7 +241,8 @@ func (am *AddressManager) Ban(addressToBan *appmessage.NetAddress) error {
|
||||
}
|
||||
}
|
||||
|
||||
return am.store.addBanned(keyToBan, addressToBan)
|
||||
address := &address{netAddress: addressToBan}
|
||||
return am.store.addBanned(keyToBan, address)
|
||||
}
|
||||
|
||||
// Unban unmarks the given address as banned
|
||||
@@ -223,7 +278,6 @@ func (am *AddressManager) IsBanned(address *appmessage.NetAddress) (bool, error)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
|
||||
}
|
||||
|
||||
func (am *AddressManager) unbanIfOldEnough(key addressKey) error {
|
||||
@@ -233,7 +287,7 @@ func (am *AddressManager) unbanIfOldEnough(key addressKey) error {
|
||||
}
|
||||
|
||||
const maxBanTime = 24 * time.Hour
|
||||
if mstime.Since(address.Timestamp) > maxBanTime {
|
||||
if mstime.Since(address.netAddress.Timestamp) > maxBanTime {
|
||||
err := am.store.removeBanned(key)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -303,3 +303,71 @@ func TestRestoreAddressManager(t *testing.T) {
|
||||
t.Fatalf("Banned address %s not returned from BannedAddresses()", addressToBan.IP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverfillAddressManager(t *testing.T) {
|
||||
addressManager, teardown := newAddressManagerForTest(t, "TestAddressManager")
|
||||
defer teardown()
|
||||
|
||||
generateTestAddresses := func(amount int) []*appmessage.NetAddress {
|
||||
testAddresses := make([]*appmessage.NetAddress, 0, amount)
|
||||
for i := byte(0); i < 128; i++ {
|
||||
for j := byte(0); j < 128; j++ {
|
||||
testAddress := &appmessage.NetAddress{IP: net.IP{1, 2, i, j}, Timestamp: mstime.Now()}
|
||||
testAddresses = append(testAddresses, testAddress)
|
||||
if len(testAddresses) == amount {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(testAddresses) == amount {
|
||||
break
|
||||
}
|
||||
}
|
||||
return testAddresses
|
||||
}
|
||||
|
||||
// Add a single test address to the address manager
|
||||
testAddress := &appmessage.NetAddress{IP: net.IP{5, 6, 0, 0}, Timestamp: mstime.Now()}
|
||||
err := addressManager.AddAddress(testAddress)
|
||||
if err != nil {
|
||||
t.Fatalf("AddAddress: %s", err)
|
||||
}
|
||||
|
||||
// Add `maxAddresses-1` addresses to the address manager
|
||||
addresses := generateTestAddresses(maxAddresses - 1)
|
||||
err = addressManager.AddAddresses(addresses...)
|
||||
if err != nil {
|
||||
t.Fatalf("AddAddresses: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that it now contains exactly `maxAddresses` entries
|
||||
returnedAddresses := addressManager.Addresses()
|
||||
if len(returnedAddresses) != maxAddresses {
|
||||
t.Fatalf("Unexpected address amount. Want: %d, got: %d", maxAddresses, len(returnedAddresses))
|
||||
}
|
||||
|
||||
// Mark the first test address as a connection failure
|
||||
err = addressManager.MarkConnectionFailure(testAddress)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkConnectionFailure: %s", err)
|
||||
}
|
||||
|
||||
// Add one more address to the address manager
|
||||
err = addressManager.AddAddress(&appmessage.NetAddress{IP: net.IP{7, 8, 0, 0}, Timestamp: mstime.Now()})
|
||||
if err != nil {
|
||||
t.Fatalf("AddAddress: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that it now still contains exactly `maxAddresses` entries
|
||||
returnedAddresses = addressManager.Addresses()
|
||||
if len(returnedAddresses) != maxAddresses {
|
||||
t.Fatalf("Unexpected address amount. Want: %d, got: %d", maxAddresses, len(returnedAddresses))
|
||||
}
|
||||
|
||||
// Make sure that the first address is no longer in the
|
||||
// connection manager
|
||||
for _, address := range returnedAddresses {
|
||||
if address.IP.Equal(testAddress.IP) {
|
||||
t.Fatalf("Unexpectedly found testAddress returned addresses")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
@@ -13,15 +14,15 @@ var bannedAddressBucket = database.MakeBucket([]byte("banned-addresses"))
|
||||
|
||||
type addressStore struct {
|
||||
database database.Database
|
||||
notBannedAddresses map[addressKey]*appmessage.NetAddress
|
||||
bannedAddresses map[ipv6]*appmessage.NetAddress
|
||||
notBannedAddresses map[addressKey]*address
|
||||
bannedAddresses map[ipv6]*address
|
||||
}
|
||||
|
||||
func newAddressStore(database database.Database) (*addressStore, error) {
|
||||
addressStore := &addressStore{
|
||||
database: database,
|
||||
notBannedAddresses: map[addressKey]*appmessage.NetAddress{},
|
||||
bannedAddresses: map[ipv6]*appmessage.NetAddress{},
|
||||
notBannedAddresses: map[addressKey]*address{},
|
||||
bannedAddresses: map[ipv6]*address{},
|
||||
}
|
||||
err := addressStore.restoreNotBannedAddresses()
|
||||
if err != nil {
|
||||
@@ -56,7 +57,7 @@ func (as *addressStore) restoreNotBannedAddresses() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netAddress := as.deserializeNetAddress(serializedNetAddress)
|
||||
netAddress := as.deserializeAddress(serializedNetAddress)
|
||||
as.notBannedAddresses[key] = netAddress
|
||||
}
|
||||
return nil
|
||||
@@ -80,13 +81,17 @@ func (as *addressStore) restoreBannedAddresses() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netAddress := as.deserializeNetAddress(serializedNetAddress)
|
||||
netAddress := as.deserializeAddress(serializedNetAddress)
|
||||
as.bannedAddresses[ipv6] = netAddress
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (as *addressStore) add(key addressKey, address *appmessage.NetAddress) error {
|
||||
func (as *addressStore) notBannedCount() int {
|
||||
return len(as.notBannedAddresses)
|
||||
}
|
||||
|
||||
func (as *addressStore) add(key addressKey, address *address) error {
|
||||
if _, ok := as.notBannedAddresses[key]; ok {
|
||||
return nil
|
||||
}
|
||||
@@ -94,10 +99,28 @@ func (as *addressStore) add(key addressKey, address *appmessage.NetAddress) erro
|
||||
as.notBannedAddresses[key] = address
|
||||
|
||||
databaseKey := as.notBannedDatabaseKey(key)
|
||||
serializedAddress := as.serializeNetAddress(address)
|
||||
serializedAddress := as.serializeAddress(address)
|
||||
return as.database.Put(databaseKey, serializedAddress)
|
||||
}
|
||||
|
||||
// updateNotBanned updates the not-banned address collection
|
||||
func (as *addressStore) updateNotBanned(key addressKey, address *address) error {
|
||||
if _, ok := as.notBannedAddresses[key]; !ok {
|
||||
return errors.Errorf("address %s is not in the store", address.netAddress.TCPAddress())
|
||||
}
|
||||
|
||||
as.notBannedAddresses[key] = address
|
||||
|
||||
databaseKey := as.notBannedDatabaseKey(key)
|
||||
serializedAddress := as.serializeAddress(address)
|
||||
return as.database.Put(databaseKey, serializedAddress)
|
||||
}
|
||||
|
||||
func (as *addressStore) getNotBanned(key addressKey) (*address, bool) {
|
||||
address, ok := as.notBannedAddresses[key]
|
||||
return address, ok
|
||||
}
|
||||
|
||||
func (as *addressStore) remove(key addressKey) error {
|
||||
delete(as.notBannedAddresses, key)
|
||||
|
||||
@@ -105,21 +128,29 @@ func (as *addressStore) remove(key addressKey) error {
|
||||
return as.database.Delete(databaseKey)
|
||||
}
|
||||
|
||||
func (as *addressStore) getAllNotBanned() []*appmessage.NetAddress {
|
||||
addresses := make([]*appmessage.NetAddress, 0, len(as.notBannedAddresses))
|
||||
func (as *addressStore) getAllNotBanned() []*address {
|
||||
addresses := make([]*address, 0, len(as.notBannedAddresses))
|
||||
for _, address := range as.notBannedAddresses {
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (as *addressStore) getAllNotBannedWithout(ignoredAddresses []*appmessage.NetAddress) []*appmessage.NetAddress {
|
||||
func (as *addressStore) getAllNotBannedNetAddresses() []*appmessage.NetAddress {
|
||||
addresses := make([]*appmessage.NetAddress, 0, len(as.notBannedAddresses))
|
||||
for _, address := range as.notBannedAddresses {
|
||||
addresses = append(addresses, address.netAddress)
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (as *addressStore) getAllNotBannedNetAddressesWithout(ignoredAddresses []*appmessage.NetAddress) []*appmessage.NetAddress {
|
||||
ignoredKeys := netAddressesKeys(ignoredAddresses)
|
||||
|
||||
addresses := make([]*appmessage.NetAddress, 0, len(as.notBannedAddresses))
|
||||
for key, address := range as.notBannedAddresses {
|
||||
if !ignoredKeys[key] {
|
||||
addresses = append(addresses, address)
|
||||
addresses = append(addresses, address.netAddress)
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
@@ -130,7 +161,7 @@ func (as *addressStore) isNotBanned(key addressKey) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (as *addressStore) addBanned(key addressKey, address *appmessage.NetAddress) error {
|
||||
func (as *addressStore) addBanned(key addressKey, address *address) error {
|
||||
if _, ok := as.bannedAddresses[key.address]; ok {
|
||||
return nil
|
||||
}
|
||||
@@ -138,7 +169,7 @@ func (as *addressStore) addBanned(key addressKey, address *appmessage.NetAddress
|
||||
as.bannedAddresses[key.address] = address
|
||||
|
||||
databaseKey := as.bannedDatabaseKey(key)
|
||||
serializedAddress := as.serializeNetAddress(address)
|
||||
serializedAddress := as.serializeAddress(address)
|
||||
return as.database.Put(databaseKey, serializedAddress)
|
||||
}
|
||||
|
||||
@@ -149,10 +180,10 @@ func (as *addressStore) removeBanned(key addressKey) error {
|
||||
return as.database.Delete(databaseKey)
|
||||
}
|
||||
|
||||
func (as *addressStore) getAllBanned() []*appmessage.NetAddress {
|
||||
func (as *addressStore) getAllBannedNetAddresses() []*appmessage.NetAddress {
|
||||
bannedAddresses := make([]*appmessage.NetAddress, 0, len(as.bannedAddresses))
|
||||
for _, bannedAddress := range as.bannedAddresses {
|
||||
bannedAddresses = append(bannedAddresses, bannedAddress)
|
||||
bannedAddresses = append(bannedAddresses, bannedAddress.netAddress)
|
||||
}
|
||||
return bannedAddresses
|
||||
}
|
||||
@@ -162,11 +193,21 @@ func (as *addressStore) isBanned(key addressKey) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (as *addressStore) getBanned(key addressKey) (*appmessage.NetAddress, bool) {
|
||||
func (as *addressStore) getBanned(key addressKey) (*address, bool) {
|
||||
bannedAddress, ok := as.bannedAddresses[key.address]
|
||||
return bannedAddress, ok
|
||||
}
|
||||
|
||||
// netAddressKeys returns a key of the ip address to use it in maps.
|
||||
func netAddressesKeys(netAddresses []*appmessage.NetAddress) map[addressKey]bool {
|
||||
result := make(map[addressKey]bool, len(netAddresses))
|
||||
for _, netAddress := range netAddresses {
|
||||
key := netAddressKey(netAddress)
|
||||
result[key] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (as *addressStore) notBannedDatabaseKey(key addressKey) *database.Key {
|
||||
serializedKey := as.serializeAddressKey(key)
|
||||
return notBannedAddressBucket.Key(serializedKey)
|
||||
@@ -198,27 +239,32 @@ func (as *addressStore) deserializeAddressKey(serializedKey []byte) addressKey {
|
||||
}
|
||||
}
|
||||
|
||||
func (as *addressStore) serializeNetAddress(netAddress *appmessage.NetAddress) []byte {
|
||||
serializedSize := 16 + 2 + 8 // ipv6 + port + timestamp
|
||||
func (as *addressStore) serializeAddress(address *address) []byte {
|
||||
serializedSize := 16 + 2 + 8 + 8 // ipv6 + port + timestamp + connectionFailedCount
|
||||
serializedNetAddress := make([]byte, serializedSize)
|
||||
|
||||
copy(serializedNetAddress[:], netAddress.IP[:])
|
||||
binary.LittleEndian.PutUint16(serializedNetAddress[16:], netAddress.Port)
|
||||
binary.LittleEndian.PutUint64(serializedNetAddress[18:], uint64(netAddress.Timestamp.UnixMilliseconds()))
|
||||
copy(serializedNetAddress[:], address.netAddress.IP[:])
|
||||
binary.LittleEndian.PutUint16(serializedNetAddress[16:], address.netAddress.Port)
|
||||
binary.LittleEndian.PutUint64(serializedNetAddress[18:], uint64(address.netAddress.Timestamp.UnixMilliseconds()))
|
||||
binary.LittleEndian.PutUint64(serializedNetAddress[26:], uint64(address.connectionFailedCount))
|
||||
|
||||
return serializedNetAddress
|
||||
}
|
||||
|
||||
func (as *addressStore) deserializeNetAddress(serializedNetAddress []byte) *appmessage.NetAddress {
|
||||
func (as *addressStore) deserializeAddress(serializedAddress []byte) *address {
|
||||
ip := make(net.IP, 16)
|
||||
copy(ip[:], serializedNetAddress[:])
|
||||
copy(ip[:], serializedAddress[:])
|
||||
|
||||
port := binary.LittleEndian.Uint16(serializedNetAddress[16:])
|
||||
timestamp := mstime.UnixMilliseconds(int64(binary.LittleEndian.Uint64(serializedNetAddress[18:])))
|
||||
port := binary.LittleEndian.Uint16(serializedAddress[16:])
|
||||
timestamp := mstime.UnixMilliseconds(int64(binary.LittleEndian.Uint64(serializedAddress[18:])))
|
||||
connectionFailedCount := binary.LittleEndian.Uint64(serializedAddress[26:])
|
||||
|
||||
return &appmessage.NetAddress{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
Timestamp: timestamp,
|
||||
return &address{
|
||||
netAddress: &appmessage.NetAddress{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
Timestamp: timestamp,
|
||||
},
|
||||
connectionFailedCount: connectionFailedCount,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,21 +24,24 @@ func TestAddressKeySerialization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetAddressSerialization(t *testing.T) {
|
||||
addressManager, teardown := newAddressManagerForTest(t, "TestNetAddressSerialization")
|
||||
func TestAddressSerialization(t *testing.T) {
|
||||
addressManager, teardown := newAddressManagerForTest(t, "TestAddressSerialization")
|
||||
defer teardown()
|
||||
addressStore := addressManager.store
|
||||
|
||||
testAddress := &appmessage.NetAddress{
|
||||
IP: net.ParseIP("2602:100:abcd::102"),
|
||||
Port: 12345,
|
||||
Timestamp: mstime.Now(),
|
||||
testAddress := &address{
|
||||
netAddress: &appmessage.NetAddress{
|
||||
IP: net.ParseIP("2602:100:abcd::102"),
|
||||
Port: 12345,
|
||||
Timestamp: mstime.Now(),
|
||||
},
|
||||
connectionFailedCount: 98465,
|
||||
}
|
||||
|
||||
serializedTestNetAddress := addressStore.serializeNetAddress(testAddress)
|
||||
deserializedTestNetAddress := addressStore.deserializeNetAddress(serializedTestNetAddress)
|
||||
if !reflect.DeepEqual(testAddress, deserializedTestNetAddress) {
|
||||
t.Fatalf("testAddress and deserializedTestNetAddress are not equal\n"+
|
||||
"testAddress:%+v\ndeserializedTestNetAddress:%+v", testAddress, deserializedTestNetAddress)
|
||||
serializedTestAddress := addressStore.serializeAddress(testAddress)
|
||||
deserializedTestAddress := addressStore.deserializeAddress(serializedTestAddress)
|
||||
if !reflect.DeepEqual(testAddress, deserializedTestAddress) {
|
||||
t.Fatalf("testAddress and deserializedTestAddress are not equal\n"+
|
||||
"testAddress:%+v\ndeserializedTestAddress:%+v", testAddress, deserializedTestAddress)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,10 @@ func (c *ConnectionManager) checkOutgoingConnections(connSet connectionSet) {
|
||||
err := c.initiateConnection(addressString)
|
||||
if err != nil {
|
||||
log.Infof("Couldn't connect to %s: %s", addressString, err)
|
||||
c.addressManager.RemoveAddress(netAddress)
|
||||
c.addressManager.MarkConnectionFailure(netAddress)
|
||||
continue
|
||||
}
|
||||
c.addressManager.MarkConnectionSuccess(netAddress)
|
||||
|
||||
c.activeOutgoing[addressString] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@ import (
|
||||
// OnErrorHandler defines a handler function for when errors occur
|
||||
type OnErrorHandler func(err error)
|
||||
|
||||
// OnDisconnectedHandler defines a handler function for when the client disconnected
|
||||
type OnDisconnectedHandler func()
|
||||
|
||||
// GRPCClient is a gRPC-based RPC client
|
||||
type GRPCClient struct {
|
||||
stream protowire.RPC_MessageStreamClient
|
||||
onErrorHandler OnErrorHandler
|
||||
stream protowire.RPC_MessageStreamClient
|
||||
onErrorHandler OnErrorHandler
|
||||
onDisconnectedHandler OnDisconnectedHandler
|
||||
}
|
||||
|
||||
// Connect connects to the RPC server with the given address
|
||||
@@ -52,6 +56,11 @@ func (c *GRPCClient) SetOnErrorHandler(onErrorHandler OnErrorHandler) {
|
||||
c.onErrorHandler = onErrorHandler
|
||||
}
|
||||
|
||||
// SetOnDisconnectedHandler sets the client's onDisconnectedHandler
|
||||
func (c *GRPCClient) SetOnDisconnectedHandler(onDisconnectedHandler OnDisconnectedHandler) {
|
||||
c.onDisconnectedHandler = onDisconnectedHandler
|
||||
}
|
||||
|
||||
// AttachRouter attaches the given router to the client and starts
|
||||
// sending/receiving messages via it
|
||||
func (c *GRPCClient) AttachRouter(router *router.Router) {
|
||||
@@ -103,6 +112,9 @@ func (c *GRPCClient) receive() (appmessage.Message, error) {
|
||||
|
||||
func (c *GRPCClient) handleError(err error) {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if c.onDisconnectedHandler != nil {
|
||||
c.onDisconnectedHandler()
|
||||
}
|
||||
return
|
||||
}
|
||||
if errors.Is(err, router.ErrRouteClosed) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient/grpcclient"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/pkg/errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -16,32 +17,112 @@ const defaultTimeout = 30 * time.Second
|
||||
type RPCClient struct {
|
||||
*grpcclient.GRPCClient
|
||||
|
||||
rpcAddress string
|
||||
rpcRouter *rpcRouter
|
||||
rpcAddress string
|
||||
rpcRouter *rpcRouter
|
||||
isConnected uint32
|
||||
isClosed uint32
|
||||
isReconnecting uint32
|
||||
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewRPCClient creates a new RPC client
|
||||
func NewRPCClient(rpcAddress string) (*RPCClient, error) {
|
||||
rpcClient, err := grpcclient.Connect(rpcAddress)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error connecting to address %s", rpcAddress)
|
||||
rpcClient := &RPCClient{
|
||||
rpcAddress: rpcAddress,
|
||||
timeout: defaultTimeout,
|
||||
}
|
||||
err := rpcClient.connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rpcClient, nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) connect() error {
|
||||
rpcClient, err := grpcclient.Connect(c.rpcAddress)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error connecting to address %s", c.rpcAddress)
|
||||
}
|
||||
rpcClient.SetOnDisconnectedHandler(c.handleClientDisconnected)
|
||||
rpcClient.SetOnErrorHandler(c.handleClientError)
|
||||
rpcRouter, err := buildRPCRouter()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating the RPC router")
|
||||
return errors.Wrapf(err, "error creating the RPC router")
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&c.isConnected, 1)
|
||||
rpcClient.AttachRouter(rpcRouter.router)
|
||||
|
||||
log.Infof("Connected to server %s", rpcAddress)
|
||||
c.GRPCClient = rpcClient
|
||||
c.rpcRouter = rpcRouter
|
||||
|
||||
return &RPCClient{
|
||||
GRPCClient: rpcClient,
|
||||
rpcAddress: rpcAddress,
|
||||
rpcRouter: rpcRouter,
|
||||
timeout: defaultTimeout,
|
||||
}, nil
|
||||
log.Infof("Connected to %s", c.rpcAddress)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) disconnect() error {
|
||||
c.rpcRouter.router.Close()
|
||||
err := c.GRPCClient.Disconnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Disconnected from %s", c.rpcAddress)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reconnect forces the client to attempt to reconnect to the address
|
||||
// this client initially was connected to
|
||||
func (c *RPCClient) Reconnect() error {
|
||||
if atomic.LoadUint32(&c.isClosed) == 1 {
|
||||
return errors.Errorf("Cannot reconnect from a closed client")
|
||||
}
|
||||
|
||||
// Protect against multiple threads attempting to reconnect at the same time
|
||||
swapped := atomic.CompareAndSwapUint32(&c.isReconnecting, 0, 1)
|
||||
if !swapped {
|
||||
// Already reconnecting
|
||||
return nil
|
||||
}
|
||||
defer atomic.StoreUint32(&c.isReconnecting, 0)
|
||||
|
||||
log.Warnf("Attempting to reconnect to %s", c.rpcAddress)
|
||||
|
||||
// Disconnect if we're connected
|
||||
if atomic.LoadUint32(&c.isConnected) == 1 {
|
||||
err := c.disconnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to connect until we succeed
|
||||
for {
|
||||
err := c.connect()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Warnf("Could not automatically reconnect to %s: %s", c.rpcAddress, err)
|
||||
|
||||
const retryDelay = 10 * time.Second
|
||||
log.Warnf("Retrying in %s", retryDelay)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) handleClientDisconnected() {
|
||||
atomic.StoreUint32(&c.isConnected, 0)
|
||||
if atomic.LoadUint32(&c.isClosed) == 0 {
|
||||
err := c.Reconnect()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) handleClientError(err error) {
|
||||
log.Warnf("Received error from client: %s", err)
|
||||
c.handleClientDisconnected()
|
||||
}
|
||||
|
||||
// SetTimeout sets the timeout by which to wait for RPC responses
|
||||
@@ -50,8 +131,13 @@ func (c *RPCClient) SetTimeout(timeout time.Duration) {
|
||||
}
|
||||
|
||||
// Close closes the RPC client
|
||||
func (c *RPCClient) Close() {
|
||||
func (c *RPCClient) Close() error {
|
||||
swapped := atomic.CompareAndSwapUint32(&c.isClosed, 0, 1)
|
||||
if !swapped {
|
||||
return errors.Errorf("Cannot close a client that had already been closed")
|
||||
}
|
||||
c.rpcRouter.router.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Address returns the address the RPC client connected to
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -178,8 +177,8 @@ func useDirOrCreateTemp(dataDir, tempName string) (string, error) {
|
||||
}
|
||||
|
||||
func mineOnTips(client *rpc.Client) (appmessage.RejectReason, error) {
|
||||
fakePublicKeyHash := make([]byte, blake2b.Size256)
|
||||
addr, err := util.NewAddressPubKeyHash(fakePublicKeyHash, activeConfig().NetParams().Prefix)
|
||||
fakePublicKey := make([]byte, util.PublicKeySize)
|
||||
addr, err := util.NewAddressPublicKey(fakePublicKey, activeConfig().NetParams().Prefix)
|
||||
if err != nil {
|
||||
return appmessage.RejectReasonNone, err
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func generateAddress() (util.Address, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return util.NewAddressPubKeyHashFromPublicKey(pubKeySerialized[:], activeConfig().ActiveNetParams.Prefix)
|
||||
return util.NewAddressPublicKey(pubKeySerialized[:], activeConfig().ActiveNetParams.Prefix)
|
||||
}
|
||||
|
||||
func areTipsAreEqual(resultA, resultB *appmessage.GetBlockDAGInfoResponseMessage) bool {
|
||||
|
||||
@@ -18,14 +18,14 @@ const (
|
||||
rpcAddress2 = "127.0.0.1:12346"
|
||||
rpcAddress3 = "127.0.0.1:12347"
|
||||
|
||||
miningAddress1 = "kaspasim:qr79e37hxdgkn4xjjmfxvqvayc5gsmsql2660d08u9ej9vnc8lzcywr265u64"
|
||||
miningAddress1PrivateKey = "0ec5d7308f65717f3f0c3e4d962d73056c1c255a16593b3989589281b51ad5bc"
|
||||
miningAddress1 = "kaspasim:qqqqnc0pxg7qw3qkc7l6sge8kfhsvvyt7mkw8uamtndqup27ftnd6c769gn66"
|
||||
miningAddress1PrivateKey = "0d81045b0deb2af36a25403c2154c87aa82d89dd337b575bae27ce7f5de53cee"
|
||||
|
||||
miningAddress2 = "kaspasim:qpvr825ypd2fzq779yl83zvte2r4wlgxwra625rgthk9jj96d4cxgsegwryhg"
|
||||
miningAddress2PrivateKey = "2a2e99d4a5c3e6d4add69e7baf66b9c7a2f17e74fad86cbd36a3a6815cecc10e"
|
||||
miningAddress2 = "kaspasim:qqqqnc0pxg7qw3qkc7l6sge8kfhsvvyt7mkw8uamtndqup27ftnd6c769gn66"
|
||||
miningAddress2PrivateKey = "0d81045b0deb2af36a25403c2154c87aa82d89dd337b575bae27ce7f5de53cee"
|
||||
|
||||
miningAddress3 = "kaspasim:qpvr825ypd2fzq779yl83zvte2r4wlgxwra625rgthk9jj96d4cxgsegwryhg"
|
||||
miningAddress3PrivateKey = "2a2e99d4a5c3e6d4add69e7baf66b9c7a2f17e74fad86cbd36a3a6815cecc10e"
|
||||
miningAddress3 = "kaspasim:qqq754f2gdcjcnykwuwwr60c82rh5u6mxxe7yqxljnrxz9fu0h95kduq9ezng"
|
||||
miningAddress3PrivateKey = "f6c8f31fd359cbb97007034780bc4021f6ad01c6bc10499b79849efd4cc7ca39"
|
||||
|
||||
defaultTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
167
util/address.go
167
util/address.go
@@ -20,8 +20,11 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
// PubKeyHash addresses always have the version byte set to 0.
|
||||
pubKeyHashAddrID = 0x00
|
||||
// PubKey addresses always have the version byte set to 0.
|
||||
pubKeyAddrID = 0x00
|
||||
|
||||
// PubKey addresses always have the version byte set to 1.
|
||||
pubKeyECDSAAddrID = 0x01
|
||||
|
||||
// ScriptHash addresses always have the version byte set to 8.
|
||||
scriptHashAddrID = 0x08
|
||||
@@ -79,16 +82,16 @@ func (prefix Bech32Prefix) String() string {
|
||||
}
|
||||
|
||||
// encodeAddress returns a human-readable payment address given a network prefix
|
||||
// and a blake2b hash which encodes the kaspa network and address type. It is used
|
||||
// in both pay-to-pubkey-hash (P2PKH) and pay-to-script-hash (P2SH) address
|
||||
// and a payload which encodes the kaspa network and address type. It is used
|
||||
// in both pay-to-pubkey (P2PK) and pay-to-script-hash (P2SH) address
|
||||
// encoding.
|
||||
func encodeAddress(prefix Bech32Prefix, hash256 []byte, version byte) string {
|
||||
return bech32.Encode(prefix.String(), hash256[:blake2b.Size256], version)
|
||||
func encodeAddress(prefix Bech32Prefix, payload []byte, version byte) string {
|
||||
return bech32.Encode(prefix.String(), payload, version)
|
||||
}
|
||||
|
||||
// Address is an interface type for any type of destination a transaction
|
||||
// output may spend to. This includes pay-to-pubkey (P2PK), pay-to-pubkey-hash
|
||||
// (P2PKH), and pay-to-script-hash (P2SH). Address is designed to be generic
|
||||
// output may spend to. This includes pay-to-pubkey (P2PK)
|
||||
// and pay-to-script-hash (P2SH). Address is designed to be generic
|
||||
// enough that other kinds of addresses may be added in the future without
|
||||
// changing the decoding and encoding API.
|
||||
type Address interface {
|
||||
@@ -98,7 +101,7 @@ type Address interface {
|
||||
// Please note that String differs subtly from EncodeAddress: String
|
||||
// will return the value as a string without any conversion, while
|
||||
// EncodeAddress may convert destination types (for example,
|
||||
// converting pubkeys to P2PKH addresses) before encoding as a
|
||||
// converting pubkeys to P2PK addresses) before encoding as a
|
||||
// payment address string.
|
||||
String() string
|
||||
|
||||
@@ -139,95 +142,143 @@ func DecodeAddress(addr string, expectedPrefix Bech32Prefix) (Address, error) {
|
||||
prefix)
|
||||
}
|
||||
|
||||
// Switch on decoded length to determine the type.
|
||||
switch len(decoded) {
|
||||
case blake2b.Size256: // P2PKH or P2SH
|
||||
switch version {
|
||||
case pubKeyHashAddrID:
|
||||
return newAddressPubKeyHash(prefix, decoded)
|
||||
case scriptHashAddrID:
|
||||
return newAddressScriptHashFromHash(prefix, decoded)
|
||||
default:
|
||||
return nil, ErrUnknownAddressType
|
||||
}
|
||||
switch version {
|
||||
case pubKeyAddrID:
|
||||
return newAddressPubKey(prefix, decoded)
|
||||
case pubKeyECDSAAddrID:
|
||||
return newAddressPubKeyECDSA(prefix, decoded)
|
||||
case scriptHashAddrID:
|
||||
return newAddressScriptHashFromHash(prefix, decoded)
|
||||
default:
|
||||
return nil, errors.New("decoded address is of unknown size")
|
||||
return nil, ErrUnknownAddressType
|
||||
}
|
||||
}
|
||||
|
||||
// AddressPubKeyHash is an Address for a pay-to-pubkey-hash (P2PKH)
|
||||
// PublicKeySize is the public key size for a schnorr public key
|
||||
const PublicKeySize = 32
|
||||
|
||||
// AddressPublicKey is an Address for a pay-to-pubkey (P2PK)
|
||||
// transaction.
|
||||
type AddressPubKeyHash struct {
|
||||
prefix Bech32Prefix
|
||||
hash [blake2b.Size256]byte
|
||||
type AddressPublicKey struct {
|
||||
prefix Bech32Prefix
|
||||
publicKey [PublicKeySize]byte
|
||||
}
|
||||
|
||||
// NewAddressPubKeyHashFromPublicKey returns a new AddressPubKeyHash from given public key
|
||||
func NewAddressPubKeyHashFromPublicKey(publicKey []byte, prefix Bech32Prefix) (*AddressPubKeyHash, error) {
|
||||
pkHash := HashBlake2b(publicKey)
|
||||
return newAddressPubKeyHash(prefix, pkHash)
|
||||
}
|
||||
|
||||
// NewAddressPubKeyHash returns a new AddressPubKeyHash. pkHash mustbe 20
|
||||
// NewAddressPublicKey returns a new AddressPublicKey. publicKey must be 32
|
||||
// bytes.
|
||||
func NewAddressPubKeyHash(pkHash []byte, prefix Bech32Prefix) (*AddressPubKeyHash, error) {
|
||||
return newAddressPubKeyHash(prefix, pkHash)
|
||||
func NewAddressPublicKey(publicKey []byte, prefix Bech32Prefix) (*AddressPublicKey, error) {
|
||||
return newAddressPubKey(prefix, publicKey)
|
||||
}
|
||||
|
||||
// newAddressPubKeyHash is the internal API to create a pubkey hash address
|
||||
// newAddressPubKey is the internal API to create a pubkey address
|
||||
// with a known leading identifier byte for a network, rather than looking
|
||||
// it up through its parameters. This is useful when creating a new address
|
||||
// structure from a string encoding where the identifier byte is already
|
||||
// known.
|
||||
func newAddressPubKeyHash(prefix Bech32Prefix, pkHash []byte) (*AddressPubKeyHash, error) {
|
||||
// Check for a valid pubkey hash length.
|
||||
if len(pkHash) != blake2b.Size256 {
|
||||
return nil, errors.Errorf("pkHash must be %d bytes", blake2b.Size256)
|
||||
func newAddressPubKey(prefix Bech32Prefix, publicKey []byte) (*AddressPublicKey, error) {
|
||||
// Check for a valid pubkey length.
|
||||
if len(publicKey) != PublicKeySize {
|
||||
return nil, errors.Errorf("publicKey must be %d bytes", PublicKeySize)
|
||||
}
|
||||
|
||||
addr := &AddressPubKeyHash{prefix: prefix}
|
||||
copy(addr.hash[:], pkHash)
|
||||
addr := &AddressPublicKey{prefix: prefix}
|
||||
copy(addr.publicKey[:], publicKey)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// EncodeAddress returns the string encoding of a pay-to-pubkey-hash
|
||||
// EncodeAddress returns the string encoding of a pay-to-pubkey
|
||||
// address. Part of the Address interface.
|
||||
func (a *AddressPubKeyHash) EncodeAddress() string {
|
||||
return encodeAddress(a.prefix, a.hash[:], pubKeyHashAddrID)
|
||||
func (a *AddressPublicKey) EncodeAddress() string {
|
||||
return encodeAddress(a.prefix, a.publicKey[:], pubKeyAddrID)
|
||||
}
|
||||
|
||||
// ScriptAddress returns the bytes to be included in a txout script to pay
|
||||
// to a pubkey hash. Part of the Address interface.
|
||||
func (a *AddressPubKeyHash) ScriptAddress() []byte {
|
||||
return a.hash[:]
|
||||
// to a pubkey. Part of the Address interface.
|
||||
func (a *AddressPublicKey) ScriptAddress() []byte {
|
||||
return a.publicKey[:]
|
||||
}
|
||||
|
||||
// IsForPrefix returns whether or not the pay-to-pubkey-hash address is associated
|
||||
// IsForPrefix returns whether or not the pay-to-pubkey address is associated
|
||||
// with the passed kaspa network.
|
||||
func (a *AddressPubKeyHash) IsForPrefix(prefix Bech32Prefix) bool {
|
||||
func (a *AddressPublicKey) IsForPrefix(prefix Bech32Prefix) bool {
|
||||
return a.prefix == prefix
|
||||
}
|
||||
|
||||
// Prefix returns the prefix for this address
|
||||
func (a *AddressPubKeyHash) Prefix() Bech32Prefix {
|
||||
func (a *AddressPublicKey) Prefix() Bech32Prefix {
|
||||
return a.prefix
|
||||
}
|
||||
|
||||
// String returns a human-readable string for the pay-to-pubkey-hash address.
|
||||
// String returns a human-readable string for the pay-to-pubkey address.
|
||||
// This is equivalent to calling EncodeAddress, but is provided so the type can
|
||||
// be used as a fmt.Stringer.
|
||||
func (a *AddressPubKeyHash) String() string {
|
||||
func (a *AddressPublicKey) String() string {
|
||||
return a.EncodeAddress()
|
||||
}
|
||||
|
||||
// HashBlake2b returns the underlying array of the pubkey hash. This can be useful
|
||||
// when an array is more appropiate than a slice (for example, when used as map
|
||||
// keys).
|
||||
func (a *AddressPubKeyHash) HashBlake2b() *[blake2b.Size256]byte {
|
||||
return &a.hash
|
||||
// PublicKeySizeECDSA is the public key size for an ECDSA public key
|
||||
const PublicKeySizeECDSA = 33
|
||||
|
||||
// AddressPublicKeyECDSA is an Address for a pay-to-pubkey (P2PK)
|
||||
// ECDSA transaction.
|
||||
type AddressPublicKeyECDSA struct {
|
||||
prefix Bech32Prefix
|
||||
publicKey [PublicKeySizeECDSA]byte
|
||||
}
|
||||
|
||||
// AddressScriptHash is an Address for a pay-to-script-hash (P2SH)
|
||||
// NewAddressPublicKeyECDSA returns a new AddressPublicKeyECDSA. publicKey must be 33
|
||||
// bytes.
|
||||
func NewAddressPublicKeyECDSA(publicKey []byte, prefix Bech32Prefix) (*AddressPublicKeyECDSA, error) {
|
||||
return newAddressPubKeyECDSA(prefix, publicKey)
|
||||
}
|
||||
|
||||
// newAddressPubKeyECDSA is the internal API to create an ECDSA pubkey address
|
||||
// with a known leading identifier byte for a network, rather than looking
|
||||
// it up through its parameters. This is useful when creating a new address
|
||||
// structure from a string encoding where the identifier byte is already
|
||||
// known.
|
||||
func newAddressPubKeyECDSA(prefix Bech32Prefix, publicKey []byte) (*AddressPublicKeyECDSA, error) {
|
||||
// Check for a valid pubkey length.
|
||||
if len(publicKey) != PublicKeySizeECDSA {
|
||||
return nil, errors.Errorf("publicKey must be %d bytes", PublicKeySizeECDSA)
|
||||
}
|
||||
|
||||
addr := &AddressPublicKeyECDSA{prefix: prefix}
|
||||
copy(addr.publicKey[:], publicKey)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// EncodeAddress returns the string encoding of a pay-to-pubkey
|
||||
// address. Part of the Address interface.
|
||||
func (a *AddressPublicKeyECDSA) EncodeAddress() string {
|
||||
return encodeAddress(a.prefix, a.publicKey[:], pubKeyECDSAAddrID)
|
||||
}
|
||||
|
||||
// ScriptAddress returns the bytes to be included in a txout script to pay
|
||||
// to a pubkey. Part of the Address interface.
|
||||
func (a *AddressPublicKeyECDSA) ScriptAddress() []byte {
|
||||
return a.publicKey[:]
|
||||
}
|
||||
|
||||
// IsForPrefix returns whether or not the pay-to-pubkey address is associated
|
||||
// with the passed kaspa network.
|
||||
func (a *AddressPublicKeyECDSA) IsForPrefix(prefix Bech32Prefix) bool {
|
||||
return a.prefix == prefix
|
||||
}
|
||||
|
||||
// Prefix returns the prefix for this address
|
||||
func (a *AddressPublicKeyECDSA) Prefix() Bech32Prefix {
|
||||
return a.prefix
|
||||
}
|
||||
|
||||
// String returns a human-readable string for the pay-to-pubkey address.
|
||||
// This is equivalent to calling EncodeAddress, but is provided so the type can
|
||||
// be used as a fmt.Stringer.
|
||||
func (a *AddressPublicKeyECDSA) String() string {
|
||||
return a.EncodeAddress()
|
||||
}
|
||||
|
||||
// AddressScriptHash is an Address for a pay-to-script-publicKey (P2SH)
|
||||
// transaction.
|
||||
type AddressScriptHash struct {
|
||||
prefix Bech32Prefix
|
||||
|
||||
@@ -26,99 +26,125 @@ func TestAddresses(t *testing.T) {
|
||||
passedPrefix util.Bech32Prefix
|
||||
expectedPrefix util.Bech32Prefix
|
||||
}{
|
||||
// Positive P2PKH tests.
|
||||
// Positive P2PK tests.
|
||||
{
|
||||
name: "mainnet p2pkh",
|
||||
name: "mainnet p2pk",
|
||||
addr: "kaspa:qr35ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35cdv0dy335",
|
||||
encoded: "kaspa:qr35ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35cdv0dy335",
|
||||
valid: true,
|
||||
result: util.TstAddressPubKeyHash(
|
||||
result: util.TstAddressPubKey(
|
||||
util.Bech32PrefixKaspa,
|
||||
[blake2b.Size256]byte{
|
||||
[util.PublicKeySize]byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c,
|
||||
}),
|
||||
f: func() (util.Address, error) {
|
||||
pkHash := []byte{
|
||||
publicKey := []byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c}
|
||||
return util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspa)
|
||||
return util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspa)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixUnknown,
|
||||
expectedPrefix: util.Bech32PrefixKaspa,
|
||||
},
|
||||
{
|
||||
name: "mainnet p2pkh 2",
|
||||
name: "mainnet p2pk 2",
|
||||
addr: "kaspa:qq80qvqs0lfxuzmt7sz3909ze6camq9d4t35ennsep3hxfe7ln35cvfqgz3z8",
|
||||
encoded: "kaspa:qq80qvqs0lfxuzmt7sz3909ze6camq9d4t35ennsep3hxfe7ln35cvfqgz3z8",
|
||||
valid: true,
|
||||
result: util.TstAddressPubKeyHash(
|
||||
result: util.TstAddressPubKey(
|
||||
util.Bech32PrefixKaspa,
|
||||
[blake2b.Size256]byte{
|
||||
[util.PublicKeySize]byte{
|
||||
0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4,
|
||||
0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c,
|
||||
}),
|
||||
f: func() (util.Address, error) {
|
||||
pkHash := []byte{
|
||||
publicKey := []byte{
|
||||
0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4,
|
||||
0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c,
|
||||
}
|
||||
return util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspa)
|
||||
return util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspa)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixKaspa,
|
||||
expectedPrefix: util.Bech32PrefixKaspa,
|
||||
},
|
||||
{
|
||||
name: "testnet p2pkh",
|
||||
name: "testnet p2pk",
|
||||
addr: "kaspatest:qputx94qseratdmjs0j395mq8u03er0x3l35ennsep3hxfe7ln35ckquw528z",
|
||||
encoded: "kaspatest:qputx94qseratdmjs0j395mq8u03er0x3l35ennsep3hxfe7ln35ckquw528z",
|
||||
valid: true,
|
||||
result: util.TstAddressPubKeyHash(
|
||||
result: util.TstAddressPubKey(
|
||||
util.Bech32PrefixKaspaTest,
|
||||
[blake2b.Size256]byte{
|
||||
[util.PublicKeySize]byte{
|
||||
0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83,
|
||||
0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c,
|
||||
}),
|
||||
f: func() (util.Address, error) {
|
||||
pkHash := []byte{
|
||||
publicKey := []byte{
|
||||
0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83,
|
||||
0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c,
|
||||
}
|
||||
return util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspaTest)
|
||||
return util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspaTest)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixKaspaTest,
|
||||
expectedPrefix: util.Bech32PrefixKaspaTest,
|
||||
},
|
||||
|
||||
// Negative P2PKH tests.
|
||||
// ECDSA P2PK tests.
|
||||
{
|
||||
name: "p2pkh wrong hash length",
|
||||
name: "mainnet ecdsa p2pk",
|
||||
addr: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
||||
encoded: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
||||
valid: true,
|
||||
result: util.TstAddressPubKeyECDSA(
|
||||
util.Bech32PrefixKaspa,
|
||||
[util.PublicKeySizeECDSA]byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c, 0xaa,
|
||||
}),
|
||||
f: func() (util.Address, error) {
|
||||
publicKey := []byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c, 0xaa}
|
||||
return util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixUnknown,
|
||||
expectedPrefix: util.Bech32PrefixKaspa,
|
||||
},
|
||||
|
||||
// Negative P2PK tests.
|
||||
{
|
||||
name: "p2pk wrong public key length",
|
||||
addr: "",
|
||||
valid: false,
|
||||
f: func() (util.Address, error) {
|
||||
pkHash := []byte{
|
||||
publicKey := []byte{
|
||||
0x00, 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b,
|
||||
0xf4, 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad,
|
||||
0xaa}
|
||||
return util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspa)
|
||||
return util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspa)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixKaspa,
|
||||
expectedPrefix: util.Bech32PrefixKaspa,
|
||||
},
|
||||
{
|
||||
name: "p2pkh bad checksum",
|
||||
name: "p2pk bad checksum",
|
||||
addr: "kaspa:qr35ennsep3hxfe7lnz5ee7j5jgmkjswss74as46gx",
|
||||
valid: false,
|
||||
passedPrefix: util.Bech32PrefixKaspa,
|
||||
@@ -168,12 +194,9 @@ func TestAddresses(t *testing.T) {
|
||||
expectedPrefix: util.Bech32PrefixKaspa,
|
||||
},
|
||||
{
|
||||
// Taken from transactions:
|
||||
// output: b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d
|
||||
// input: (not yet redeemed at time test was written)
|
||||
name: "mainnet p2sh 2",
|
||||
addr: "kaspa:pr5vxqxg0xrwl2zvxlq9rxffqx00sm44ksqqqqqqqqqqqqqqqqqqq33flv3je",
|
||||
encoded: "kaspa:pr5vxqxg0xrwl2zvxlq9rxffqx00sm44ksqqqqqqqqqqqqqqqqqqq33flv3je",
|
||||
addr: "kaspa:pr5vxqxg0xrwl2zvxlq9rxffqx00sm44kn5vxqxg0xrwl2zvxl5vxyhvsake2",
|
||||
encoded: "kaspa:pr5vxqxg0xrwl2zvxlq9rxffqx00sm44kn5vxqxg0xrwl2zvxl5vxyhvsake2",
|
||||
valid: true,
|
||||
result: util.TstAddressScriptHash(
|
||||
util.Bech32PrefixKaspa,
|
||||
@@ -197,8 +220,8 @@ func TestAddresses(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "testnet p2sh",
|
||||
addr: "kaspatest:przhjdpv93xfygpqtckdc2zkzuzqeyj2pgqqqqqqqqqqqqqqqqqqqyjpt4duk",
|
||||
encoded: "kaspatest:przhjdpv93xfygpqtckdc2zkzuzqeyj2pgqqqqqqqqqqqqqqqqqqqyjpt4duk",
|
||||
addr: "kaspatest:przhjdpv93xfygpqtckdc2zkzuzqeyj2pt5vxqxg0xrwl2zvxl5vx35yyy2h9",
|
||||
encoded: "kaspatest:przhjdpv93xfygpqtckdc2zkzuzqeyj2pt5vxqxg0xrwl2zvxl5vx35yyy2h9",
|
||||
valid: true,
|
||||
result: util.TstAddressScriptHash(
|
||||
util.Bech32PrefixKaspaTest,
|
||||
@@ -270,23 +293,25 @@ func TestAddresses(t *testing.T) {
|
||||
// Perform type-specific calculations.
|
||||
var saddr []byte
|
||||
switch decoded.(type) {
|
||||
case *util.AddressPubKeyHash:
|
||||
saddr = util.TstAddressSAddr(encoded)
|
||||
case *util.AddressPublicKey:
|
||||
saddr = util.TstAddressSAddrP2PK(encoded)
|
||||
|
||||
case *util.AddressPublicKeyECDSA:
|
||||
saddr = util.TstAddressSAddrP2PKECDSA(encoded)
|
||||
|
||||
case *util.AddressScriptHash:
|
||||
saddr = util.TstAddressSAddr(encoded)
|
||||
saddr = util.TstAddressSAddrP2SH(encoded)
|
||||
}
|
||||
|
||||
// Check script address, as well as the HashBlake2b method for P2PKH and
|
||||
// P2SH addresses.
|
||||
// Check script address, as well as the HashBlake2b method for P2SH addresses.
|
||||
if !bytes.Equal(saddr, decoded.ScriptAddress()) {
|
||||
t.Errorf("%v: script addresses do not match:\n%x != \n%x",
|
||||
test.name, saddr, decoded.ScriptAddress())
|
||||
return
|
||||
}
|
||||
switch a := decoded.(type) {
|
||||
case *util.AddressPubKeyHash:
|
||||
if h := a.HashBlake2b()[:]; !bytes.Equal(saddr, h) {
|
||||
case *util.AddressPublicKey:
|
||||
if h := a.ScriptAddress()[:]; !bytes.Equal(saddr, h) {
|
||||
t.Errorf("%v: hashes do not match:\n%x != \n%x",
|
||||
test.name, saddr, h)
|
||||
return
|
||||
@@ -335,6 +360,18 @@ func TestAddresses(t *testing.T) {
|
||||
test.name)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(addr, decoded) {
|
||||
t.Errorf("%v: created address does not match the decoded address",
|
||||
test.name)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(addr, decoded) {
|
||||
t.Errorf("%v: created address does not match the decoded address",
|
||||
test.name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +394,7 @@ func TestDecodeAddressErrorConditions(t *testing.T) {
|
||||
{
|
||||
"kaspasim:raskzcg58mth0an",
|
||||
util.Bech32PrefixKaspaSim,
|
||||
"decoded address is of unknown size",
|
||||
"unknown address type",
|
||||
},
|
||||
{
|
||||
"kaspatest:qqq65mvpxcmajeq44n2n8vfn6u9f8l4zsy0xez0tzw",
|
||||
|
||||
@@ -18,14 +18,14 @@ expensive hashing operations.
|
||||
Address Overview
|
||||
|
||||
The Address interface provides an abstraction for a kaspa address. While the
|
||||
most common type is a pay-to-pubkey-hash, kaspa already supports others and
|
||||
most common type is a pay-to-pubkey, kaspa already supports others and
|
||||
may well support more in the future. This package currently provides
|
||||
implementations for the pay-to-pubkey-hash, and pay-to-script-hash address
|
||||
implementations for the pay-to-pubkey, and pay-to-script-hash address
|
||||
types.
|
||||
|
||||
To decode/encode an address:
|
||||
|
||||
addrString := "kaspa:qqfgqp8l9l90zwetj84k2jcac2m8falvvyy8xjtnhd"
|
||||
addrString := "kaspa:qqj9fg59mptxkr9j0y53j5mwurcmda5mtza9n6v9pm9uj8h0wgk6uma5pvumr"
|
||||
defaultPrefix := util.Bech32PrefixKaspa
|
||||
addr, err := util.DecodeAddress(addrString, defaultPrefix)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,10 +22,17 @@ func TstAppDataDir(goos, appName string, roaming bool) string {
|
||||
return appDir(goos, appName, roaming)
|
||||
}
|
||||
|
||||
func TstAddressPubKeyHash(prefix Bech32Prefix, hash [blake2b.Size256]byte) *AddressPubKeyHash {
|
||||
return &AddressPubKeyHash{
|
||||
prefix: prefix,
|
||||
hash: hash,
|
||||
func TstAddressPubKey(prefix Bech32Prefix, hash [PublicKeySize]byte) *AddressPublicKey {
|
||||
return &AddressPublicKey{
|
||||
prefix: prefix,
|
||||
publicKey: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func TstAddressPubKeyECDSA(prefix Bech32Prefix, hash [PublicKeySizeECDSA]byte) *AddressPublicKeyECDSA {
|
||||
return &AddressPublicKeyECDSA{
|
||||
prefix: prefix,
|
||||
publicKey: hash,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +47,22 @@ func TstAddressScriptHash(prefix Bech32Prefix, hash [blake2b.Size256]byte) *Addr
|
||||
}
|
||||
|
||||
// TstAddressSAddr returns the expected script address bytes for
|
||||
// P2PKH and P2SH kaspa addresses.
|
||||
func TstAddressSAddr(addr string) []byte {
|
||||
// P2PK kaspa addresses.
|
||||
func TstAddressSAddrP2PK(addr string) []byte {
|
||||
_, decoded, _, _ := bech32.Decode(addr)
|
||||
return decoded[:PublicKeySize]
|
||||
}
|
||||
|
||||
// TstAddressSAddr returns the expected script address bytes for
|
||||
// ECDSA P2PK kaspa addresses.
|
||||
func TstAddressSAddrP2PKECDSA(addr string) []byte {
|
||||
_, decoded, _, _ := bech32.Decode(addr)
|
||||
return decoded[:PublicKeySizeECDSA]
|
||||
}
|
||||
|
||||
// TstAddressSAddrP2SH returns the expected script address bytes for
|
||||
// P2SH kaspa addresses.
|
||||
func TstAddressSAddrP2SH(addr string) []byte {
|
||||
_, decoded, _, _ := bech32.Decode(addr)
|
||||
return decoded[:blake2b.Size256]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user