Compare commits

...

24 Commits

Author SHA1 Message Date
tal
16a83f9bac Initialize the selcted parent on GHOSTDAG. 2021-04-08 14:27:22 +03:00
tal
7d20ee6b58 Adds a comment to exported function (GHOSTDAG). 2021-04-08 14:10:03 +03:00
tal
1ab05c3fbc Redesign the code of GHOSTDAG function. 2021-04-08 14:04:28 +03:00
Svarog
347dd8fc4b Support resolveBlockStatus without separate stagingAreas for usage of testConsensus (#1666) 2021-04-08 11:26:17 +03:00
Ori Newman
d2cccd2829 Add ECDSA support to the wallet (#1664)
* Add ECDSA support to the wallet

* Fix genkeypair

* Fix typo and rename var
2021-04-06 17:25:09 +03:00
Ori Newman
7186f83095 Add OpCheckMultiSigECDSA (#1663)
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
2021-04-06 16:29:16 +03:00
Ori Newman
5c394c2951 Add PubKeyECDSATy (#1662) 2021-04-06 15:56:31 +03:00
Ori Newman
a786cdc15e Add ECDSA support (#1657)
* Add ECDSA support

* Add domain separation to ECDSA sighash

* Use InfallibleWrite instead of Write

* Rename funcs

* Fix wrong use if vm.sigCache

* Add TestCalculateSignatureHashECDSA

* Add consts

* Fix comment and test name

* Move consts to the top

* Fix comment
2021-04-06 14:27:18 +03:00
Ori Newman
6dd3d4a9e7 Add dump unencrypted data sub command to the wallet (#1661) 2021-04-06 12:29:13 +03:00
stasatdaglabs
73b36f12f0 Implement importing private keys into the wallet (#1655)
* Implement importing private keys into the wallet.

* Fix bad --import default.

* Fix typo in --import annotation.

* Make go lint happy.

* Make go lint happier.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-04-05 18:10:33 +03:00
stasatdaglabs
a795a9e619 Add a size limit to the address manager (#1652)
* Remove a random address from the address manager if it's full.

* Implement TestOverfillAddressManager.

* Add connectionFailedCount to addresses.

* Mark connection failures.

* Mark connection successes.

* Implement removing by most connection failures.

* Expand TestOverfillAddressManager.

* Add comments.

* Use a better method for finding the address with the greatest connectionFailedCount.

* Fix a comment.

* Compare addresses by IP in TestOverfillAddressManager.

* Add a comment for updateNotBanned.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-04-05 17:56:13 +03:00
Ori Newman
0be1bba408 Fix TestAddresses (#1656) 2021-04-05 16:24:22 +03:00
Ori Newman
6afc06ce58 Replace p2pkh with p2pk (#1650)
* Replace p2pkh with p2pk

* Fix tests

* Fix comments and variable names

* Add README.md for genkeypair

* Rename pubkey->publicKey

* Rename p2pkh to p2pk

* Use util.PublicKeySize where needed

* Remove redundant pointer

* Fix comment

* Rename pubKey->publicKey
2021-04-05 14:35:34 +03:00
stasatdaglabs
d01a213f3d Add a show-address subcommand to kaspawallet (#1653)
* Add a show-address subcommand to kaspawallet.

* Update the description of the key-file command line parameter.
2021-04-05 14:22:03 +03:00
stasatdaglabs
7ad8ce521c Implement reconnection logic within the RPC client (#1643)
* Add a reconnect mechanism to RPCClient.

* Fix Reconnect().

* Connect the internal reconnection logic to the miner reconnection logic.

* Rename shouldReconnect to isClosed.

* Move safe reconnection logic from the miner to rpcclient.

* Remove sleep from HandleSubmitBlock.

* Properly handle client errors and only disconnect if we're already connected.

* Make go lint happy.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-04-05 13:57:28 +03:00
Ori Newman
86ba80a091 Improve wallet functionality (#1636)
* Add basic wallet library

* Add CLI

* Add multisig support

* Add persistence to wallet

* Add tests

* go mod tidy

* Fix lint errors

* Fix wallet send command

* Always use the password as byte slice

* Remove redundant empty string

* Use different salt per private key

* Don't sign a signed transaction

* Add comment

* Remove old wallet

* Change directory permissions

* Use NormalizeRPCServerAddress

* Fix compilation errors
2021-03-31 15:58:22 +03:00
Ori Newman
088e2114c2 Disconnect from RPC client after finishing the simple sync test (#1641) 2021-03-31 13:44:42 +03:00
stasatdaglabs
2854d91688 Add missing call to broadcastTransactionsAfterBlockAdded (#1639)
* Add missing call to broadcastTransactionsAfterBlockAdded.

* Fix a comment.

* Fix a comment some more.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-03-31 10:28:02 +03:00
Ori Newman
af10b59181 Use go-secp256k1 v0.0.5 (#1640) 2021-03-30 18:01:56 +03:00
stasatdaglabs
c5b0394bbc In RPC, use RPCTransactions and RPCBlocks instead of TransactionMessages and BlockMessages (#1609)
* Replace BlockMessage with RpcBlock in rpc.proto.

* Convert everything in kaspad to use RPCBlocks and fix tests.

* Fix compilation errors in stability tests and the miner.

* Update TransactionVerboseData in rpc.proto.

* Update TransactionVerboseData in the rest of kaspad.

* Make golint happy.

* Include RpcTransactionVerboseData in RpcTransaction instead of the other way around.

* Regenerate rpc.pb.go after merge.

* Update appmessage types.

* Update appmessage request and response types.

* Reimplement conversion functions between appmessage.RPCTransaction and protowire.RpcTransaction.

* Extract RpcBlockHeader toAppMessage/fromAppMessage out of RpcBlock.

* Fix compilation errors in getBlock, getBlocks, and submitBlock.

* Fix compilation errors in getMempoolEntry.

* Fix compilation errors in notifyBlockAdded.

* Update verbosedata.go.

* Fix compilation errors in getBlock and getBlocks.

* Fix compilation errors in getBlocks tests.

* Fix conversions between getBlocks message types.

* Fix integration tests.

* Fix a comment.

* Add selectedParent to the verbose block response.
2021-03-30 17:43:02 +03:00
Ori Newman
9266d179a9 Add a test with two signed inputs (#1628)
* Add TestSigningTwoInputs

* Rename fundingBlockHash to block1Hash

* Fix error message

Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
2021-03-30 16:54:54 +03:00
Ori Newman
321792778e Add mass limit to mempool (#1627)
* Add mass limit to mempool

* Pass only params instead of multiple configuration options

* Remove acceptNonStd from mempool constructor

* Remove acceptNonStd from mempool constructor

* Fix test compilation
2021-03-30 15:37:56 +03:00
talelbaz
70f3fa9893 Update miningManager test (#1593)
* [NOD-1429] add mining manager unit tests

* [NOD-1429] Add additional test

* found a bug, so stopped working on this test until the bug will be fix.

* Update miningmanager_test.go test.

* Delete payloadHash field - not used anymore in the current version.

* Change the condition for comparing slices instead of pointers.

* Fix due to review notes - change names, use testutils.CreateTransaction function and adds comments.

* Changes after fetch&merge to v0.10.0-dev

* Create a new function createChildTxWhenParentTxWasAddedByConsensus and add a comment

* Add an argument to create_transaction function and fix review notes

* Optimization

* Change to blockID(instead of the all transaction) in the error messages and fix review notes

* Change to blockID(instead of the all transaction) in the error messages and fix review notes

* Change format of error messages.

* Change name ofa variable

* Use go:embed to embed sample-kaspad.conf (only on go1.16)

* Revert "Use go:embed to embed sample-kaspad.conf (only on go1.16)"

This reverts commit bd28052b92.

Co-authored-by: karim1king <karimkaspersky@yahoo.com>
Co-authored-by: tal <tal@daglabs.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-03-30 13:52:40 +03:00
Svarog
4e18031483 Resolve each block status in it's own staging area (#1634) 2021-03-30 11:04:43 +03:00
136 changed files with 8889 additions and 4709 deletions

View File

@@ -3,6 +3,7 @@ package appmessage
import (
"encoding/hex"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
@@ -294,3 +295,70 @@ func DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs(
}
return domainOutpointAndUTXOEntryPairs
}
// DomainBlockToRPCBlock converts DomainBlocks to RPCBlocks
func DomainBlockToRPCBlock(block *externalapi.DomainBlock) *RPCBlock {
header := &RPCBlockHeader{
Version: uint32(block.Header.Version()),
ParentHashes: hashes.ToStrings(block.Header.ParentHashes()),
HashMerkleRoot: block.Header.HashMerkleRoot().String(),
AcceptedIDMerkleRoot: block.Header.AcceptedIDMerkleRoot().String(),
UTXOCommitment: block.Header.UTXOCommitment().String(),
Timestamp: block.Header.TimeInMilliseconds(),
Bits: block.Header.Bits(),
Nonce: block.Header.Nonce(),
}
transactions := make([]*RPCTransaction, len(block.Transactions))
for i, transaction := range block.Transactions {
transactions[i] = DomainTransactionToRPCTransaction(transaction)
}
return &RPCBlock{
Header: header,
Transactions: transactions,
}
}
// RPCBlockToDomainBlock converts `block` into a DomainBlock
func RPCBlockToDomainBlock(block *RPCBlock) (*externalapi.DomainBlock, error) {
parentHashes := make([]*externalapi.DomainHash, len(block.Header.ParentHashes))
for i, parentHash := range block.Header.ParentHashes {
domainParentHashes, err := externalapi.NewDomainHashFromString(parentHash)
if err != nil {
return nil, err
}
parentHashes[i] = domainParentHashes
}
hashMerkleRoot, err := externalapi.NewDomainHashFromString(block.Header.HashMerkleRoot)
if err != nil {
return nil, err
}
acceptedIDMerkleRoot, err := externalapi.NewDomainHashFromString(block.Header.AcceptedIDMerkleRoot)
if err != nil {
return nil, err
}
utxoCommitment, err := externalapi.NewDomainHashFromString(block.Header.UTXOCommitment)
if err != nil {
return nil, err
}
header := blockheader.NewImmutableBlockHeader(
uint16(block.Header.Version),
parentHashes,
hashMerkleRoot,
acceptedIDMerkleRoot,
utxoCommitment,
block.Header.Timestamp,
block.Header.Bits,
block.Header.Nonce)
transactions := make([]*externalapi.DomainTransaction, len(block.Transactions))
for i, transaction := range block.Transactions {
domainTransaction, err := RPCTransactionToDomainTransaction(transaction)
if err != nil {
return nil, err
}
transactions[i] = domainTransaction
}
return &externalapi.DomainBlock{
Header: header,
Transactions: transactions,
}, nil
}

View File

@@ -25,7 +25,7 @@ func NewGetBlockRequestMessage(hash string, includeTransactionVerboseData bool)
// its respective RPC message
type GetBlockResponseMessage struct {
baseMessage
BlockVerboseData *BlockVerboseData
Block *RPCBlock
Error *RPCError
}
@@ -39,70 +39,3 @@ func (msg *GetBlockResponseMessage) Command() MessageCommand {
func NewGetBlockResponseMessage() *GetBlockResponseMessage {
return &GetBlockResponseMessage{}
}
// BlockVerboseData holds verbose data about a block
type BlockVerboseData struct {
Hash string
Version uint16
VersionHex string
HashMerkleRoot string
AcceptedIDMerkleRoot string
UTXOCommitment string
TxIDs []string
TransactionVerboseData []*TransactionVerboseData
Time int64
Nonce uint64
Bits string
Difficulty float64
ParentHashes []string
ChildrenHashes []string
SelectedParentHash string
BlueScore uint64
IsHeaderOnly bool
}
// TransactionVerboseData holds verbose data about a transaction
type TransactionVerboseData struct {
TxID string
Hash string
Size uint64
Version uint16
LockTime uint64
SubnetworkID string
Gas uint64
Payload string
TransactionVerboseInputs []*TransactionVerboseInput
TransactionVerboseOutputs []*TransactionVerboseOutput
BlockHash string
Time uint64
BlockTime uint64
}
// TransactionVerboseInput holds data about a transaction input
type TransactionVerboseInput struct {
TxID string
OutputIndex uint32
ScriptSig *ScriptSig
Sequence uint64
}
// ScriptSig holds data about a script signature
type ScriptSig struct {
Asm string
Hex string
}
// TransactionVerboseOutput holds data about a transaction output
type TransactionVerboseOutput struct {
Value uint64
Index uint32
ScriptPubKey *ScriptPubKeyResult
}
// ScriptPubKeyResult holds data about a script public key
type ScriptPubKeyResult struct {
Hex string
Type string
Address string
Version uint16
}

View File

@@ -23,7 +23,7 @@ func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateReque
// its respective RPC message
type GetBlockTemplateResponseMessage struct {
baseMessage
MsgBlock *MsgBlock
Block *RPCBlock
IsSynced bool
Error *RPCError
@@ -35,9 +35,9 @@ func (msg *GetBlockTemplateResponseMessage) Command() MessageCommand {
}
// NewGetBlockTemplateResponseMessage returns a instance of the message
func NewGetBlockTemplateResponseMessage(msgBlock *MsgBlock, isSynced bool) *GetBlockTemplateResponseMessage {
func NewGetBlockTemplateResponseMessage(block *RPCBlock, isSynced bool) *GetBlockTemplateResponseMessage {
return &GetBlockTemplateResponseMessage{
MsgBlock: msgBlock,
Block: block,
IsSynced: isSynced,
}
}

View File

@@ -5,7 +5,7 @@ package appmessage
type GetBlocksRequestMessage struct {
baseMessage
LowHash string
IncludeBlockVerboseData bool
IncludeBlocks bool
IncludeTransactionVerboseData bool
}
@@ -15,11 +15,11 @@ func (msg *GetBlocksRequestMessage) Command() MessageCommand {
}
// NewGetBlocksRequestMessage returns a instance of the message
func NewGetBlocksRequestMessage(lowHash string, includeBlockVerboseData bool,
func NewGetBlocksRequestMessage(lowHash string, includeBlocks bool,
includeTransactionVerboseData bool) *GetBlocksRequestMessage {
return &GetBlocksRequestMessage{
LowHash: lowHash,
IncludeBlockVerboseData: includeBlockVerboseData,
IncludeBlocks: includeBlocks,
IncludeTransactionVerboseData: includeTransactionVerboseData,
}
}
@@ -28,8 +28,8 @@ func NewGetBlocksRequestMessage(lowHash string, includeBlockVerboseData bool,
// its respective RPC message
type GetBlocksResponseMessage struct {
baseMessage
BlockHashes []string
BlockVerboseData []*BlockVerboseData
BlockHashes []string
Blocks []*RPCBlock
Error *RPCError
}
@@ -40,11 +40,6 @@ func (msg *GetBlocksResponseMessage) Command() MessageCommand {
}
// NewGetBlocksResponseMessage returns a instance of the message
func NewGetBlocksResponseMessage(blockHashes []string, blockHexes []string,
blockVerboseData []*BlockVerboseData) *GetBlocksResponseMessage {
return &GetBlocksResponseMessage{
BlockHashes: blockHashes,
BlockVerboseData: blockVerboseData,
}
func NewGetBlocksResponseMessage() *GetBlocksResponseMessage {
return &GetBlocksResponseMessage{}
}

View File

@@ -28,8 +28,8 @@ type GetMempoolEntryResponseMessage struct {
// MempoolEntry represents a transaction in the mempool.
type MempoolEntry struct {
Fee uint64
TransactionVerboseData *TransactionVerboseData
Fee uint64
Transaction *RPCTransaction
}
// Command returns the protocol command string for the message
@@ -38,11 +38,11 @@ func (msg *GetMempoolEntryResponseMessage) Command() MessageCommand {
}
// NewGetMempoolEntryResponseMessage returns a instance of the message
func NewGetMempoolEntryResponseMessage(fee uint64, transactionVerboseData *TransactionVerboseData) *GetMempoolEntryResponseMessage {
func NewGetMempoolEntryResponseMessage(fee uint64, transaction *RPCTransaction) *GetMempoolEntryResponseMessage {
return &GetMempoolEntryResponseMessage{
Entry: &MempoolEntry{
Fee: fee,
TransactionVerboseData: transactionVerboseData,
Fee: fee,
Transaction: transaction,
},
}
}

View File

@@ -37,8 +37,7 @@ func NewNotifyBlockAddedResponseMessage() *NotifyBlockAddedResponseMessage {
// its respective RPC message
type BlockAddedNotificationMessage struct {
baseMessage
Block *MsgBlock
BlockVerboseData *BlockVerboseData
Block *RPCBlock
}
// Command returns the protocol command string for the message
@@ -47,9 +46,8 @@ func (msg *BlockAddedNotificationMessage) Command() MessageCommand {
}
// NewBlockAddedNotificationMessage returns a instance of the message
func NewBlockAddedNotificationMessage(block *MsgBlock, blockVerboseData *BlockVerboseData) *BlockAddedNotificationMessage {
func NewBlockAddedNotificationMessage(block *RPCBlock) *BlockAddedNotificationMessage {
return &BlockAddedNotificationMessage{
Block: block,
BlockVerboseData: blockVerboseData,
Block: block,
}
}

View File

@@ -4,7 +4,7 @@ package appmessage
// its respective RPC message
type SubmitBlockRequestMessage struct {
baseMessage
Block *MsgBlock
Block *RPCBlock
}
// Command returns the protocol command string for the message
@@ -13,7 +13,7 @@ func (msg *SubmitBlockRequestMessage) Command() MessageCommand {
}
// NewSubmitBlockRequestMessage returns a instance of the message
func NewSubmitBlockRequestMessage(block *MsgBlock) *SubmitBlockRequestMessage {
func NewSubmitBlockRequestMessage(block *RPCBlock) *SubmitBlockRequestMessage {
return &SubmitBlockRequestMessage{
Block: block,
}
@@ -57,3 +57,35 @@ func (msg *SubmitBlockResponseMessage) Command() MessageCommand {
func NewSubmitBlockResponseMessage() *SubmitBlockResponseMessage {
return &SubmitBlockResponseMessage{}
}
// RPCBlock is a kaspad block representation meant to be
// used over RPC
type RPCBlock struct {
Header *RPCBlockHeader
Transactions []*RPCTransaction
VerboseData *RPCBlockVerboseData
}
// RPCBlockHeader is a kaspad block header representation meant to be
// used over RPC
type RPCBlockHeader struct {
Version uint32
ParentHashes []string
HashMerkleRoot string
AcceptedIDMerkleRoot string
UTXOCommitment string
Timestamp int64
Bits uint32
Nonce uint64
}
// RPCBlockVerboseData holds verbose data about a block
type RPCBlockVerboseData struct {
Hash string
Difficulty float64
SelectedParentHash string
TransactionIDs []string
IsHeaderOnly bool
BlueScore uint64
ChildrenHashes []string
}

View File

@@ -50,6 +50,7 @@ type RPCTransaction struct {
SubnetworkID string
Gas uint64
Payload string
VerboseData *RPCTransactionVerboseData
}
// RPCTransactionInput is a kaspad transaction input representation
@@ -58,6 +59,7 @@ type RPCTransactionInput struct {
PreviousOutpoint *RPCOutpoint
SignatureScript string
Sequence uint64
VerboseData *RPCTransactionInputVerboseData
}
// RPCScriptPublicKey is a kaspad ScriptPublicKey representation
@@ -71,6 +73,7 @@ type RPCScriptPublicKey struct {
type RPCTransactionOutput struct {
Amount uint64
ScriptPublicKey *RPCScriptPublicKey
VerboseData *RPCTransactionOutputVerboseData
}
// RPCOutpoint is a kaspad outpoint representation meant to be used
@@ -88,3 +91,22 @@ type RPCUTXOEntry struct {
BlockDAAScore uint64
IsCoinbase bool
}
// RPCTransactionVerboseData holds verbose data about a transaction
type RPCTransactionVerboseData struct {
TransactionID string
Hash string
Size uint64
BlockHash string
BlockTime uint64
}
// RPCTransactionInputVerboseData holds data about a transaction input
type RPCTransactionInputVerboseData struct {
}
// RPCTransactionOutputVerboseData holds data about a transaction output
type RPCTransactionOutputVerboseData struct {
ScriptPublicKeyType string
ScriptPublicKeyAddress string
}

View File

@@ -37,12 +37,14 @@ func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock,
newBlockInsertionResults = append(newBlockInsertionResults, unorphaningResult.blockInsertionResult)
}
allAcceptedTransactions := make([]*externalapi.DomainTransaction, 0)
for i, newBlock := range newBlocks {
log.Debugf("OnNewBlock: passing block %s transactions to mining manager", hash)
_, err = f.Domain().MiningManager().HandleNewBlockTransactions(newBlock.Transactions)
acceptedTransactions, err := f.Domain().MiningManager().HandleNewBlockTransactions(newBlock.Transactions)
if err != nil {
return err
}
allAcceptedTransactions = append(allAcceptedTransactions, acceptedTransactions...)
if f.onBlockAddedToDAGHandler != nil {
log.Debugf("OnNewBlock: calling f.onBlockAddedToDAGHandler for block %s", hash)
@@ -54,7 +56,7 @@ func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock,
}
}
return nil
return f.broadcastTransactionsAfterBlockAdded(newBlocks, allAcceptedTransactions)
}
// OnPruningPointUTXOSetOverride calls the handler function whenever the UTXO set
@@ -67,9 +69,9 @@ func (f *FlowContext) OnPruningPointUTXOSetOverride() error {
}
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
block *externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
addedBlocks []*externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
f.updateTransactionsToRebroadcast(block)
f.updateTransactionsToRebroadcast(addedBlocks)
// Don't relay transactions when in IBD.
if f.IsIBDRunning() {

View File

@@ -25,14 +25,17 @@ func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction) error {
return f.Broadcast(inv)
}
func (f *FlowContext) updateTransactionsToRebroadcast(block *externalapi.DomainBlock) {
func (f *FlowContext) updateTransactionsToRebroadcast(addedBlocks []*externalapi.DomainBlock) {
f.transactionsToRebroadcastLock.Lock()
defer f.transactionsToRebroadcastLock.Unlock()
// Note: if the block is red, its transactions won't be rebroadcasted
// anymore, although they are not included in the UTXO set.
// This is probably ok, since red blocks are quite rare.
for _, tx := range block.Transactions {
delete(f.transactionsToRebroadcast, *consensushashing.TransactionID(tx))
for _, block := range addedBlocks {
// Note: if a transaction is included in the DAG but not accepted,
// it won't be rebroadcast anymore, although it is not included in
// the UTXO set
for _, tx := range block.Transactions {
delete(f.transactionsToRebroadcast, *consensushashing.TransactionID(tx))
}
}
}

View File

@@ -129,7 +129,7 @@ type fakeRelayInvsContext struct {
rwLock sync.RWMutex
}
func (f *fakeRelayInvsContext) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
func (f *fakeRelayInvsContext) GetBlockRelations(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, *externalapi.DomainHash, []*externalapi.DomainHash, error) {
panic(errors.Errorf("called unimplemented function from test '%s'", f.testName))
}

View File

@@ -69,12 +69,12 @@ func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock, blockIns
return err
}
msgBlock := appmessage.DomainBlockToMsgBlock(block)
blockVerboseData, err := m.context.BuildBlockVerboseData(block.Header, block, false)
rpcBlock := appmessage.DomainBlockToRPCBlock(block)
err = m.context.PopulateBlockWithVerboseData(rpcBlock, block.Header, block, false)
if err != nil {
return err
}
blockAddedNotification := appmessage.NewBlockAddedNotificationMessage(msgBlock, blockVerboseData)
blockAddedNotification := appmessage.NewBlockAddedNotificationMessage(rpcBlock)
return m.context.NotificationManager.NotifyBlockAdded(blockAddedNotification)
}

View File

@@ -2,15 +2,10 @@ package rpccontext
import (
"encoding/hex"
"fmt"
difficultyPackage "github.com/kaspanet/kaspad/util/difficulty"
"github.com/pkg/errors"
"math"
"math/big"
"strconv"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
@@ -26,79 +21,6 @@ import (
// ErrBuildBlockVerboseDataInvalidBlock indicates that a block that was given to BuildBlockVerboseData is invalid.
var ErrBuildBlockVerboseDataInvalidBlock = errors.New("ErrBuildBlockVerboseDataInvalidBlock")
// BuildBlockVerboseData builds a BlockVerboseData from the given blockHeader.
// A block may optionally also be given if it's available in the calling context.
func (ctx *Context) BuildBlockVerboseData(blockHeader externalapi.BlockHeader, block *externalapi.DomainBlock,
includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "BuildBlockVerboseData")
defer onEnd()
hash := consensushashing.HeaderHash(blockHeader)
blockInfo, err := ctx.Domain.Consensus().GetBlockInfo(hash)
if err != nil {
return nil, err
}
if blockInfo.BlockStatus == externalapi.StatusInvalid {
return nil, errors.Wrap(ErrBuildBlockVerboseDataInvalidBlock, "cannot build verbose data for "+
"invalid block")
}
childrenHashes, err := ctx.Domain.Consensus().GetBlockChildren(hash)
if err != nil {
return nil, err
}
result := &appmessage.BlockVerboseData{
Hash: hash.String(),
Version: blockHeader.Version(),
VersionHex: fmt.Sprintf("%08x", blockHeader.Version()),
HashMerkleRoot: blockHeader.HashMerkleRoot().String(),
AcceptedIDMerkleRoot: blockHeader.AcceptedIDMerkleRoot().String(),
UTXOCommitment: blockHeader.UTXOCommitment().String(),
ParentHashes: hashes.ToStrings(blockHeader.ParentHashes()),
ChildrenHashes: hashes.ToStrings(childrenHashes),
Nonce: blockHeader.Nonce(),
Time: blockHeader.TimeInMilliseconds(),
Bits: strconv.FormatInt(int64(blockHeader.Bits()), 16),
Difficulty: ctx.GetDifficultyRatio(blockHeader.Bits(), ctx.Config.ActiveNetParams),
BlueScore: blockInfo.BlueScore,
IsHeaderOnly: blockInfo.BlockStatus == externalapi.StatusHeaderOnly,
}
if blockInfo.BlockStatus != externalapi.StatusHeaderOnly {
if block == nil {
block, err = ctx.Domain.Consensus().GetBlock(hash)
if err != nil {
return nil, err
}
}
txIDs := make([]string, len(block.Transactions))
for i, tx := range block.Transactions {
txIDs[i] = consensushashing.TransactionID(tx).String()
}
result.TxIDs = txIDs
if includeTransactionVerboseData {
transactionVerboseData := make([]*appmessage.TransactionVerboseData, len(block.Transactions))
for i, tx := range block.Transactions {
txID := consensushashing.TransactionID(tx).String()
data, err := ctx.BuildTransactionVerboseData(tx, txID, blockHeader, hash.String())
if err != nil {
return nil, err
}
transactionVerboseData[i] = data
}
result.TransactionVerboseData = transactionVerboseData
}
}
return result, nil
}
// GetDifficultyRatio returns the proof-of-work difficulty as a multiple of the
// minimum difficulty using the passed bits field from the header of a block.
func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) float64 {
@@ -106,7 +28,7 @@ func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) fl
// converted back to a number. Note this is not the same as the proof of
// work limit directly because the block difficulty is encoded in a block
// with the compact form which loses precision.
target := difficulty.CompactToBig(bits)
target := difficultyPackage.CompactToBig(bits)
difficulty := new(big.Rat).SetFrac(params.PowMax, target)
diff, _ := difficulty.Float64()
@@ -117,100 +39,125 @@ func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) fl
return diff
}
// BuildTransactionVerboseData builds a TransactionVerboseData from
// the given parameters
func (ctx *Context) BuildTransactionVerboseData(tx *externalapi.DomainTransaction, txID string,
blockHeader externalapi.BlockHeader, blockHash string) (
*appmessage.TransactionVerboseData, error) {
// PopulateBlockWithVerboseData populates the given `block` with verbose
// data from `domainBlockHeader` and optionally from `domainBlock`
func (ctx *Context) PopulateBlockWithVerboseData(block *appmessage.RPCBlock, domainBlockHeader externalapi.BlockHeader,
domainBlock *externalapi.DomainBlock, includeTransactionVerboseData bool) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "BuildTransactionVerboseData")
defer onEnd()
blockHash := consensushashing.HeaderHash(domainBlockHeader)
txReply := &appmessage.TransactionVerboseData{
TxID: txID,
Hash: consensushashing.TransactionHash(tx).String(),
Size: estimatedsize.TransactionEstimatedSerializedSize(tx),
TransactionVerboseInputs: ctx.buildTransactionVerboseInputs(tx),
TransactionVerboseOutputs: ctx.buildTransactionVerboseOutputs(tx, nil),
Version: tx.Version,
LockTime: tx.LockTime,
SubnetworkID: tx.SubnetworkID.String(),
Gas: tx.Gas,
Payload: hex.EncodeToString(tx.Payload),
blockInfo, err := ctx.Domain.Consensus().GetBlockInfo(blockHash)
if err != nil {
return err
}
if blockHeader != nil {
txReply.Time = uint64(blockHeader.TimeInMilliseconds())
txReply.BlockTime = uint64(blockHeader.TimeInMilliseconds())
txReply.BlockHash = blockHash
if blockInfo.BlockStatus == externalapi.StatusInvalid {
return errors.Wrap(ErrBuildBlockVerboseDataInvalidBlock, "cannot build verbose data for "+
"invalid block")
}
return txReply, nil
}
_, selectedParentHash, childrenHashes, err := ctx.Domain.Consensus().GetBlockRelations(blockHash)
if err != nil {
return err
}
func (ctx *Context) buildTransactionVerboseInputs(tx *externalapi.DomainTransaction) []*appmessage.TransactionVerboseInput {
inputs := make([]*appmessage.TransactionVerboseInput, len(tx.Inputs))
for i, transactionInput := range tx.Inputs {
// The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the
// error here.
disbuf, _ := txscript.DisasmString(constants.MaxScriptPublicKeyVersion, transactionInput.SignatureScript)
block.VerboseData = &appmessage.RPCBlockVerboseData{
Hash: blockHash.String(),
Difficulty: ctx.GetDifficultyRatio(domainBlockHeader.Bits(), ctx.Config.ActiveNetParams),
ChildrenHashes: hashes.ToStrings(childrenHashes),
SelectedParentHash: selectedParentHash.String(),
IsHeaderOnly: blockInfo.BlockStatus == externalapi.StatusHeaderOnly,
BlueScore: blockInfo.BlueScore,
}
input := &appmessage.TransactionVerboseInput{}
input.TxID = transactionInput.PreviousOutpoint.TransactionID.String()
input.OutputIndex = transactionInput.PreviousOutpoint.Index
input.Sequence = transactionInput.Sequence
input.ScriptSig = &appmessage.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(transactionInput.SignatureScript),
if blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
return nil
}
// Get the block if we didn't receive it previously
if domainBlock == nil {
domainBlock, err = ctx.Domain.Consensus().GetBlock(blockHash)
if err != nil {
return err
}
inputs[i] = input
}
return inputs
}
transactionIDs := make([]string, len(domainBlock.Transactions))
for i, transaction := range domainBlock.Transactions {
transactionIDs[i] = consensushashing.TransactionID(transaction).String()
}
block.VerboseData.TransactionIDs = transactionIDs
// buildTransactionVerboseOutputs returns a slice of JSON objects for the outputs of the passed
// transaction.
func (ctx *Context) buildTransactionVerboseOutputs(tx *externalapi.DomainTransaction, filterAddrMap map[string]struct{}) []*appmessage.TransactionVerboseOutput {
outputs := make([]*appmessage.TransactionVerboseOutput, len(tx.Outputs))
for i, transactionOutput := range tx.Outputs {
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information about
// it anyways.
scriptClass, addr, _ := txscript.ExtractScriptPubKeyAddress(
transactionOutput.ScriptPublicKey, ctx.Config.ActiveNetParams)
// Encode the addresses while checking if the address passes the
// filter when needed.
passesFilter := len(filterAddrMap) == 0
var encodedAddr string
if addr != nil {
encodedAddr = addr.EncodeAddress()
// If the filter doesn't already pass, make it pass if
// the address exists in the filter.
if _, exists := filterAddrMap[encodedAddr]; exists {
passesFilter = true
if includeTransactionVerboseData {
for _, transaction := range block.Transactions {
err := ctx.PopulateTransactionWithVerboseData(transaction, domainBlockHeader)
if err != nil {
return err
}
}
if !passesFilter {
continue
}
output := &appmessage.TransactionVerboseOutput{}
output.Index = uint32(i)
output.Value = transactionOutput.Value
output.ScriptPubKey = &appmessage.ScriptPubKeyResult{
Version: transactionOutput.ScriptPublicKey.Version,
Address: encodedAddr,
Hex: hex.EncodeToString(transactionOutput.ScriptPublicKey.Script),
Type: scriptClass.String(),
}
outputs[i] = output
}
return outputs
return nil
}
// PopulateTransactionWithVerboseData populates the given `transaction` with
// verbose data from `domainTransaction`
func (ctx *Context) PopulateTransactionWithVerboseData(
transaction *appmessage.RPCTransaction, domainBlockHeader externalapi.BlockHeader) error {
domainTransaction, err := appmessage.RPCTransactionToDomainTransaction(transaction)
if err != nil {
return err
}
transaction.VerboseData = &appmessage.RPCTransactionVerboseData{
TransactionID: consensushashing.TransactionID(domainTransaction).String(),
Hash: consensushashing.TransactionHash(domainTransaction).String(),
Size: estimatedsize.TransactionEstimatedSerializedSize(domainTransaction),
}
if domainBlockHeader != nil {
transaction.VerboseData.BlockHash = consensushashing.HeaderHash(domainBlockHeader).String()
transaction.VerboseData.BlockTime = uint64(domainBlockHeader.TimeInMilliseconds())
}
for _, input := range transaction.Inputs {
ctx.populateTransactionInputWithVerboseData(input)
}
for _, output := range transaction.Outputs {
err := ctx.populateTransactionOutputWithVerboseData(output)
if err != nil {
return err
}
}
return nil
}
func (ctx *Context) populateTransactionInputWithVerboseData(transactionInput *appmessage.RPCTransactionInput) {
transactionInput.VerboseData = &appmessage.RPCTransactionInputVerboseData{}
}
func (ctx *Context) populateTransactionOutputWithVerboseData(transactionOutput *appmessage.RPCTransactionOutput) error {
scriptPublicKey, err := hex.DecodeString(transactionOutput.ScriptPublicKey.Script)
if err != nil {
return err
}
domainScriptPublicKey := &externalapi.ScriptPublicKey{
Script: scriptPublicKey,
Version: transactionOutput.ScriptPublicKey.Version,
}
// Ignore the error here since an error means the script
// couldn't be parsed and there's no additional information about
// it anyways
scriptPublicKeyType, scriptPublicKeyAddress, _ := txscript.ExtractScriptPubKeyAddress(
domainScriptPublicKey, ctx.Config.ActiveNetParams)
var encodedScriptPublicKeyAddress string
if scriptPublicKeyAddress != nil {
encodedScriptPublicKeyAddress = scriptPublicKeyAddress.EncodeAddress()
}
transactionOutput.VerboseData = &appmessage.RPCTransactionOutputVerboseData{
ScriptPublicKeyType: scriptPublicKeyType.String(),
ScriptPublicKeyAddress: encodedScriptPublicKeyAddress,
}
return nil
}

View File

@@ -26,10 +26,12 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
errorMessage.Error = appmessage.RPCErrorf("Block %s not found", hash)
return errorMessage, nil
}
block := &externalapi.DomainBlock{Header: header}
response := appmessage.NewGetBlockResponseMessage()
response.Block = appmessage.DomainBlockToRPCBlock(block)
blockVerboseData, err := context.BuildBlockVerboseData(header, nil, getBlockRequest.IncludeTransactionVerboseData)
err = context.PopulateBlockWithVerboseData(response.Block, header, nil, getBlockRequest.IncludeTransactionVerboseData)
if err != nil {
if errors.Is(err, rpccontext.ErrBuildBlockVerboseDataInvalidBlock) {
errorMessage := &appmessage.GetBlockResponseMessage{}
@@ -39,7 +41,5 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
return nil, err
}
response.BlockVerboseData = blockVerboseData
return response, nil
}

View File

@@ -31,12 +31,12 @@ func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, reque
if err != nil {
return nil, err
}
msgBlock := appmessage.DomainBlockToMsgBlock(templateBlock)
rpcBlock := appmessage.DomainBlockToRPCBlock(templateBlock)
isSynced, err := context.ProtocolManager.ShouldMine()
if err != nil {
return nil, err
}
return appmessage.NewGetBlockTemplateResponseMessage(msgBlock, isSynced), nil
return appmessage.NewGetBlockTemplateResponseMessage(rpcBlock, isSynced), nil
}

View File

@@ -18,8 +18,8 @@ const (
func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlocksRequest := request.(*appmessage.GetBlocksRequestMessage)
// Validate that user didn't set IncludeTransactionVerboseData without setting IncludeBlockVerboseData
if !getBlocksRequest.IncludeBlockVerboseData && getBlocksRequest.IncludeTransactionVerboseData {
// Validate that user didn't set IncludeTransactionVerboseData without setting IncludeBlocks
if !getBlocksRequest.IncludeBlocks && getBlocksRequest.IncludeTransactionVerboseData {
return &appmessage.GetBlocksResponseMessage{
Error: appmessage.RPCErrorf(
"If includeTransactionVerboseData is set, then includeBlockVerboseData must be set as well"),
@@ -81,26 +81,23 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm
}
// Prepare the response
response := &appmessage.GetBlocksResponseMessage{
BlockHashes: hashes.ToStrings(blockHashes),
}
// Retrieve all block data in case BlockVerboseData was requested
if getBlocksRequest.IncludeBlockVerboseData {
response.BlockVerboseData = make([]*appmessage.BlockVerboseData, len(blockHashes))
response := appmessage.NewGetBlocksResponseMessage()
response.BlockHashes = hashes.ToStrings(blockHashes)
if getBlocksRequest.IncludeBlocks {
blocks := make([]*appmessage.RPCBlock, len(blockHashes))
for i, blockHash := range blockHashes {
blockHeader, err := context.Domain.Consensus().GetBlockHeader(blockHash)
if err != nil {
return nil, err
}
blockVerboseData, err := context.BuildBlockVerboseData(blockHeader, nil,
getBlocksRequest.IncludeTransactionVerboseData)
block := &externalapi.DomainBlock{Header: blockHeader}
blocks[i] = appmessage.DomainBlockToRPCBlock(block)
err = context.PopulateBlockWithVerboseData(blocks[i], blockHeader, nil, getBlocksRequest.IncludeTransactionVerboseData)
if err != nil {
return nil, err
}
response.BlockVerboseData[i] = blockVerboseData
}
}
return response, nil
}

View File

@@ -3,25 +3,22 @@ package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetMempoolEntries handles the respectively named RPC command
func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
transactions := context.Domain.MiningManager().AllTransactions()
entries := make([]*appmessage.MempoolEntry, 0, len(transactions))
for _, tx := range transactions {
transactionVerboseData, err := context.BuildTransactionVerboseData(
tx, consensushashing.TransactionID(tx).String(), nil, "")
for _, transaction := range transactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
entries = append(entries, &appmessage.MempoolEntry{
Fee: tx.Fee,
TransactionVerboseData: transactionVerboseData,
Fee: transaction.Fee,
Transaction: rpcTransaction,
})
}

View File

@@ -24,12 +24,11 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques
errorMessage.Error = appmessage.RPCErrorf("Transaction %s was not found", transactionID)
return errorMessage, nil
}
transactionVerboseData, err := context.BuildTransactionVerboseData(
transaction, getMempoolEntryRequest.TxID, nil, "")
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err = context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil {
return nil, err
}
return appmessage.NewGetMempoolEntryResponseMessage(transaction.Fee, transactionVerboseData), nil
return appmessage.NewGetMempoolEntryResponseMessage(transaction.Fee, rpcTransaction), nil
}

View File

@@ -14,9 +14,6 @@ import (
func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitBlockRequest := request.(*appmessage.SubmitBlockRequestMessage)
msgBlock := submitBlockRequest.Block
domainBlock := appmessage.MsgBlockToDomainBlock(msgBlock)
if context.ProtocolManager.IsIBDRunning() {
return &appmessage.SubmitBlockResponseMessage{
Error: appmessage.RPCErrorf("Block not submitted - IBD is running"),
@@ -24,7 +21,15 @@ func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request ap
}, nil
}
err := context.ProtocolManager.AddBlock(domainBlock)
domainBlock, err := appmessage.RPCBlockToDomainBlock(submitBlockRequest.Block)
if err != nil {
return &appmessage.SubmitBlockResponseMessage{
Error: appmessage.RPCErrorf("Could not parse block: %s", err),
RejectReason: appmessage.RejectReasonBlockInvalid,
}, nil
}
err = context.ProtocolManager.AddBlock(domainBlock)
if err != nil {
isProtocolOrRuleError := errors.As(err, &ruleerrors.RuleError{}) || errors.As(err, &protocolerrors.ProtocolError{})
if !isProtocolOrRuleError {

9
cmd/genkeypair/README.md Normal file
View 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
View 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
View 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)
}

View File

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

View File

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

View File

@@ -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,17 +192,29 @@ 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.Address())
return
}
templatemanager.Set(template)
}
getBlockTemplate()

View File

@@ -23,10 +23,14 @@ func Get() (*externalapi.DomainBlock, bool) {
}
// Set sets the current template to work on
func Set(template *appmessage.GetBlockTemplateResponseMessage) {
block := appmessage.MsgBlockToDomainBlock(template.MsgBlock)
func Set(template *appmessage.GetBlockTemplateResponseMessage) error {
block, err := appmessage.RPCBlockToDomainBlock(template.Block)
if err != nil {
return err
}
lock.Lock()
defer lock.Unlock()
currentTemplate = block
isSynced = template.IsSynced
return nil
}

View File

@@ -2,17 +2,28 @@ package main
import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/util"
)
func balance(conf *balanceConfig) error {
client, err := rpcclient.NewRPCClient(conf.RPCServer)
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{conf.Address})
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
if err != nil {
return err
}
addr, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{addr.String()})
if err != nil {
return err
}

View File

@@ -0,0 +1,34 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
)
func broadcast(conf *broadcastConfig) error {
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
psTxBytes, err := hex.DecodeString(conf.Transaction)
if err != nil {
return err
}
tx, err := libkaspawallet.ExtractTransaction(psTxBytes)
if err != nil {
return err
}
transactionID, err := sendTransaction(client, tx)
if err != nil {
return err
}
fmt.Println("Transaction was sent successfully")
fmt.Printf("Transaction ID: \t%s\n", transactionID)
return nil
}

View File

@@ -3,6 +3,8 @@ package main
import (
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"os"
)
@@ -19,3 +21,12 @@ func printErrorAndExit(err error) {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
func connectToRPC(params *dagconfig.Params, rpcServer string) (*rpcclient.RPCClient, error) {
rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer)
if err != nil {
return nil, err
}
return rpcclient.NewRPCClient(rpcAddress)
}

198
cmd/kaspawallet/config.go Normal file
View File

@@ -0,0 +1,198 @@
package main
import (
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"os"
"github.com/jessevdk/go-flags"
)
const (
createSubCmd = "create"
balanceSubCmd = "balance"
sendSubCmd = "send"
createUnsignedTransactionSubCmd = "create-unsigned-transaction"
signSubCmd = "sign"
broadcastSubCmd = "broadcast"
showAddressSubCmd = "show-address"
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
)
type configFlags struct {
config.NetworkFlags
}
type createConfig struct {
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" 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 (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 (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"`
config.NetworkFlags
}
type createUnsignedTransactionConfig struct {
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"`
config.NetworkFlags
}
type signConfig struct {
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
}
type broadcastConfig struct {
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Transaction string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)" required:"true"`
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)
createConf := &createConfig{}
parser.AddCommand(createSubCmd, "Creates a new wallet",
"Creates a private key and 3 public addresses, one for each of MainNet, TestNet and DevNet", createConf)
balanceConf := &balanceConfig{}
parser.AddCommand(balanceSubCmd, "Shows the balance of a public address",
"Shows the balance for a public address in Kaspa", balanceConf)
sendConf := &sendConfig{}
parser.AddCommand(sendSubCmd, "Sends a Kaspa transaction to a public address",
"Sends a Kaspa transaction to a public address", sendConf)
createUnsignedTransactionConf := &createUnsignedTransactionConfig{}
parser.AddCommand(createUnsignedTransactionSubCmd, "Create an unsigned Kaspa transaction",
"Create an unsigned Kaspa transaction", createUnsignedTransactionConf)
signConf := &signConfig{}
parser.AddCommand(signSubCmd, "Sign the given partially signed transaction",
"Sign the given partially signed transaction", signConf)
broadcastConf := &broadcastConfig{}
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 {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
os.Exit(0)
} else {
os.Exit(1)
}
return "", nil
}
switch parser.Command.Active.Name {
case createSubCmd:
combineNetworkFlags(&createConf.NetworkFlags, &cfg.NetworkFlags)
err := createConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = createConf
case balanceSubCmd:
combineNetworkFlags(&balanceConf.NetworkFlags, &cfg.NetworkFlags)
err := balanceConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = balanceConf
case sendSubCmd:
combineNetworkFlags(&sendConf.NetworkFlags, &cfg.NetworkFlags)
err := sendConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = sendConf
case createUnsignedTransactionSubCmd:
combineNetworkFlags(&createUnsignedTransactionConf.NetworkFlags, &cfg.NetworkFlags)
err := createUnsignedTransactionConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = createUnsignedTransactionConf
case signSubCmd:
combineNetworkFlags(&signConf.NetworkFlags, &cfg.NetworkFlags)
err := signConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = signConf
case broadcastSubCmd:
combineNetworkFlags(&broadcastConf.NetworkFlags, &cfg.NetworkFlags)
err := broadcastConf.ResolveNetwork(parser)
if err != nil {
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
}
func combineNetworkFlags(dst, src *config.NetworkFlags) {
dst.Testnet = dst.Testnet || src.Testnet
dst.Simnet = dst.Simnet || src.Simnet
dst.Devnet = dst.Devnet || src.Devnet
if dst.OverrideDAGParamsFile == "" {
dst.OverrideDAGParamsFile = src.OverrideDAGParamsFile
}
}

69
cmd/kaspawallet/create.go Normal file
View File

@@ -0,0 +1,69 @@
package main
import (
"bufio"
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/pkg/errors"
"os"
)
func create(conf *createConfig) error {
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
}
for i, publicKey := range publicKeys {
fmt.Printf("Public key of private key #%d:\n%x\n\n", i+1, publicKey)
}
reader := bufio.NewReader(os.Stdin)
for i := conf.NumPrivateKeys; i < conf.NumPublicKeys; i++ {
fmt.Printf("Enter public key #%d here:\n", i+1)
line, isPrefix, err := reader.ReadLine()
if err != nil {
return err
}
fmt.Println()
if isPrefix {
return errors.Errorf("Public key is too long")
}
publicKey, err := hex.DecodeString(string(line))
if err != nil {
return err
}
publicKeys = append(publicKeys, publicKey)
}
err = keys.WriteKeysFile(conf.KeysFile, encryptedPrivateKeys, publicKeys, conf.MinimumSignatures, conf.ECDSA)
if err != nil {
return err
}
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
if err != nil {
return err
}
addr, 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", addr)
return nil
}

View File

@@ -0,0 +1,61 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/util"
)
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
if err != nil {
return err
}
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.NetParams().Prefix)
if err != nil {
return err
}
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
utxos, err := fetchSpendableUTXOs(conf.NetParams(), client, fromAddress.String())
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
const feePerInput = 1000
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, sendAmountSompi, feePerInput)
if err != nil {
return err
}
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
}
fmt.Println("Created unsigned transaction")
fmt.Println(hex.EncodeToString(psTx))
return nil
}

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

View File

@@ -0,0 +1,110 @@
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, 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:")
if subtle.ConstantTimeCompare(password, confirmPassword) != 1 {
return nil, nil, errors.New("Passwords are not identical")
}
encryptedPrivateKeys = make([]*EncryptedPrivateKey, 0, numKeys)
for i := uint32(0); i < numKeys; i++ {
privateKey, publicKey, err := keyPairFunction(i)
if err != nil {
return nil, nil, err
}
publicKeys = append(publicKeys, publicKey)
encryptedPrivateKey, err := encryptPrivateKey(privateKey, password)
if err != nil {
return nil, nil, err
}
encryptedPrivateKeys = append(encryptedPrivateKeys, encryptedPrivateKey)
}
return encryptedPrivateKeys, publicKeys, nil
}
func generateSalt() ([]byte, error) {
salt := make([]byte, 16)
_, err := rand.Read(salt)
if err != nil {
return nil, err
}
return salt, nil
}
func encryptPrivateKey(privateKey []byte, password []byte) (*EncryptedPrivateKey, error) {
salt, err := generateSalt()
if err != nil {
return nil, err
}
aead, err := getAEAD(password, salt)
if err != nil {
return nil, err
}
// Select a random nonce, and leave capacity for the ciphertext.
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(privateKey)+aead.Overhead())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
// Encrypt the message and append the ciphertext to the nonce.
cipher := aead.Seal(nonce, nonce, privateKey, nil)
return &EncryptedPrivateKey{
cipher: cipher,
salt: salt,
}, nil
}

View File

@@ -0,0 +1,41 @@
package keys
import (
"fmt"
"golang.org/x/term"
"os"
"os/signal"
"syscall"
)
// getPassword was adapted from https://gist.github.com/jlinoff/e8e26b4ffa38d379c7f1891fd174a6d0#file-getpassword2-go
func getPassword(prompt string) []byte {
// Get the initial state of the terminal.
initialTermState, e1 := term.GetState(syscall.Stdin)
if e1 != nil {
panic(e1)
}
// Restore it in the event of an interrupt.
// CITATION: Konstantin Shaposhnikov - https://groups.google.com/forum/#!topic/golang-nuts/kTVAbtee9UA
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
<-c
_ = term.Restore(syscall.Stdin, initialTermState)
os.Exit(1)
}()
// Now get the password.
fmt.Print(prompt)
p, err := term.ReadPassword(syscall.Stdin)
fmt.Println()
if err != nil {
panic(err)
}
// Stop looking for ^C on the channel.
signal.Stop(c)
return p
}

View File

@@ -0,0 +1,254 @@
package keys
import (
"bufio"
"crypto/cipher"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305"
"os"
"path/filepath"
"runtime"
)
var (
defaultAppDir = util.AppDir("kaspawallet", false)
defaultKeysFile = filepath.Join(defaultAppDir, "keys.json")
)
type encryptedPrivateKeyJSON struct {
Cipher string `json:"cipher"`
Salt string `json:"salt"`
}
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
type EncryptedPrivateKey struct {
cipher []byte
salt []byte
}
// Data holds all the data related to the wallet keys
type Data struct {
encryptedPrivateKeys []*EncryptedPrivateKey
PublicKeys [][]byte
MinimumSignatures uint32
ECDSA bool
}
func (d *Data) toJSON() *keysFileJSON {
encryptedPrivateKeysJSON := make([]*encryptedPrivateKeyJSON, len(d.encryptedPrivateKeys))
for i, encryptedPrivateKey := range d.encryptedPrivateKeys {
encryptedPrivateKeysJSON[i] = &encryptedPrivateKeyJSON{
Cipher: hex.EncodeToString(encryptedPrivateKey.cipher),
Salt: hex.EncodeToString(encryptedPrivateKey.salt),
}
}
publicKeysHex := make([]string, len(d.PublicKeys))
for i, publicKey := range d.PublicKeys {
publicKeysHex[i] = hex.EncodeToString(publicKey)
}
return &keysFileJSON{
EncryptedPrivateKeys: encryptedPrivateKeysJSON,
PublicKeys: publicKeysHex,
MinimumSignatures: d.MinimumSignatures,
ECDSA: d.ECDSA,
}
}
func (d *Data) fromJSON(fileJSON *keysFileJSON) error {
d.MinimumSignatures = fileJSON.MinimumSignatures
d.ECDSA = fileJSON.ECDSA
d.encryptedPrivateKeys = make([]*EncryptedPrivateKey, len(fileJSON.EncryptedPrivateKeys))
for i, encryptedPrivateKeyJSON := range fileJSON.EncryptedPrivateKeys {
cipher, err := hex.DecodeString(encryptedPrivateKeyJSON.Cipher)
if err != nil {
return err
}
salt, err := hex.DecodeString(encryptedPrivateKeyJSON.Salt)
if err != nil {
return err
}
d.encryptedPrivateKeys[i] = &EncryptedPrivateKey{
cipher: cipher,
salt: salt,
}
}
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 {
return err
}
}
return nil
}
// DecryptPrivateKeys asks the user to enter the password for the private keys and
// returns the decrypted private keys.
func (d *Data) DecryptPrivateKeys() ([][]byte, error) {
password := getPassword("Password:")
privateKeys := make([][]byte, len(d.encryptedPrivateKeys))
for i, encryptedPrivateKey := range d.encryptedPrivateKeys {
var err error
privateKeys[i], err = decryptPrivateKey(encryptedPrivateKey, password)
if err != nil {
return nil, err
}
}
return privateKeys, nil
}
// ReadKeysFile returns the data related to the keys file
func ReadKeysFile(path string) (*Data, error) {
if path == "" {
path = defaultKeysFile
}
file, err := os.Open(path)
if err != nil {
return nil, err
}
decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields()
decodedFile := &keysFileJSON{}
err = decoder.Decode(&decodedFile)
if err != nil {
return nil, err
}
keysFile := &Data{}
err = keysFile.fromJSON(decodedFile)
if err != nil {
return nil, err
}
return keysFile, nil
}
func createFileDirectoryIfDoesntExist(path string) error {
dir := filepath.Dir(path)
exists, err := pathExists(dir)
if err != nil {
return err
}
if exists {
return nil
}
return os.MkdirAll(dir, 0600)
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// WriteKeysFile writes a keys file with the given data
func WriteKeysFile(path string,
encryptedPrivateKeys []*EncryptedPrivateKey,
publicKeys [][]byte,
minimumSignatures uint32,
ecdsa bool) error {
if path == "" {
path = defaultKeysFile
}
exists, err := pathExists(path)
if err != nil {
return err
}
if exists {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("The file %s already exists. Are you sure you want to override it (type 'y' to approve)? ", path)
line, _, err := reader.ReadLine()
if err != nil {
return err
}
if string(line) != "y" {
return errors.Errorf("aborted keys file creation")
}
}
err = createFileDirectoryIfDoesntExist(path)
if err != nil {
return err
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer file.Close()
keysFile := &Data{
encryptedPrivateKeys: encryptedPrivateKeys,
PublicKeys: publicKeys,
MinimumSignatures: minimumSignatures,
ECDSA: ecdsa,
}
encoder := json.NewEncoder(file)
err = encoder.Encode(keysFile.toJSON())
if err != nil {
return err
}
fmt.Printf("Wrote the keys into %s\n", path)
return nil
}
func getAEAD(password, salt []byte) (cipher.AEAD, error) {
key := argon2.IDKey(password, salt, 1, 64*1024, uint8(runtime.NumCPU()), 32)
return chacha20poly1305.NewX(key)
}
func decryptPrivateKey(encryptedPrivateKey *EncryptedPrivateKey, password []byte) ([]byte, error) {
aead, err := getAEAD(password, encryptedPrivateKey.salt)
if err != nil {
return nil, err
}
if len(encryptedPrivateKey.cipher) < aead.NonceSize() {
return nil, errors.New("ciphertext too short")
}
// Split nonce and ciphertext.
nonce, ciphertext := encryptedPrivateKey.cipher[:aead.NonceSize()], encryptedPrivateKey.cipher[aead.NonceSize():]
// Decrypt the message and check it wasn't tampered with.
return aead.Open(nil, nonce, ciphertext, nil)
}

View File

@@ -0,0 +1,93 @@
package libkaspawallet
import (
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
// CreateKeyPair generates a private-public key pair
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 := keyPair.SchnorrPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
}
return keyPair.SerializePrivateKey()[:], publicKeySerialized[:], nil
}
func createKeyPairECDSA() ([]byte, []byte, error) {
keyPair, err := secp256k1.GenerateECDSAPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate private key")
}
publicKey, err := keyPair.ECDSAPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to serialize public key")
}
return keyPair.Serialize()[:], publicKeySerialized[:], nil
}
// PublicKeyFromPrivateKey returns the public key associated with a private key
func PublicKeyFromPrivateKey(privateKeyBytes []byte) ([]byte, error) {
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKeyBytes)
if err != nil {
return nil, errors.Wrap(err, "Failed to 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, 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 {
if ecdsa {
return util.NewAddressPublicKeyECDSA(pubKeys[0][:], params.Prefix)
}
return util.NewAddressPublicKey(pubKeys[0][:], params.Prefix)
}
redeemScript, err := multiSigRedeemScript(pubKeys, minimumSignatures, ecdsa)
if err != nil {
return nil, err
}
return util.NewAddressScriptHash(redeemScript, params.Prefix)
}

View File

@@ -0,0 +1,3 @@
//go:generate protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative wallet.proto
package protoserialization

View File

@@ -0,0 +1,915 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.12.3
// source: wallet.proto
package protoserialization
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type PartiallySignedTransaction struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Tx *TransactionMessage `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"`
PartiallySignedInputs []*PartiallySignedInput `protobuf:"bytes,2,rep,name=partiallySignedInputs,proto3" json:"partiallySignedInputs,omitempty"`
}
func (x *PartiallySignedTransaction) Reset() {
*x = PartiallySignedTransaction{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PartiallySignedTransaction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PartiallySignedTransaction) ProtoMessage() {}
func (x *PartiallySignedTransaction) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PartiallySignedTransaction.ProtoReflect.Descriptor instead.
func (*PartiallySignedTransaction) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{0}
}
func (x *PartiallySignedTransaction) GetTx() *TransactionMessage {
if x != nil {
return x.Tx
}
return nil
}
func (x *PartiallySignedTransaction) GetPartiallySignedInputs() []*PartiallySignedInput {
if x != nil {
return x.PartiallySignedInputs
}
return nil
}
type PartiallySignedInput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RedeemScript []byte `protobuf:"bytes,1,opt,name=redeemScript,proto3" json:"redeemScript,omitempty"`
PrevOutput *TransactionOutput `protobuf:"bytes,2,opt,name=prevOutput,proto3" json:"prevOutput,omitempty"`
MinimumSignatures uint32 `protobuf:"varint,3,opt,name=minimumSignatures,proto3" json:"minimumSignatures,omitempty"`
PubKeySignaturePairs []*PubKeySignaturePair `protobuf:"bytes,4,rep,name=pubKeySignaturePairs,proto3" json:"pubKeySignaturePairs,omitempty"`
}
func (x *PartiallySignedInput) Reset() {
*x = PartiallySignedInput{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PartiallySignedInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PartiallySignedInput) ProtoMessage() {}
func (x *PartiallySignedInput) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PartiallySignedInput.ProtoReflect.Descriptor instead.
func (*PartiallySignedInput) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{1}
}
func (x *PartiallySignedInput) GetRedeemScript() []byte {
if x != nil {
return x.RedeemScript
}
return nil
}
func (x *PartiallySignedInput) GetPrevOutput() *TransactionOutput {
if x != nil {
return x.PrevOutput
}
return nil
}
func (x *PartiallySignedInput) GetMinimumSignatures() uint32 {
if x != nil {
return x.MinimumSignatures
}
return 0
}
func (x *PartiallySignedInput) GetPubKeySignaturePairs() []*PubKeySignaturePair {
if x != nil {
return x.PubKeySignaturePairs
}
return nil
}
type PubKeySignaturePair struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PubKey []byte `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
}
func (x *PubKeySignaturePair) Reset() {
*x = PubKeySignaturePair{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PubKeySignaturePair) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PubKeySignaturePair) ProtoMessage() {}
func (x *PubKeySignaturePair) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PubKeySignaturePair.ProtoReflect.Descriptor instead.
func (*PubKeySignaturePair) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{2}
}
func (x *PubKeySignaturePair) GetPubKey() []byte {
if x != nil {
return x.PubKey
}
return nil
}
func (x *PubKeySignaturePair) GetSignature() []byte {
if x != nil {
return x.Signature
}
return nil
}
type SubnetworkId struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"`
}
func (x *SubnetworkId) Reset() {
*x = SubnetworkId{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubnetworkId) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubnetworkId) ProtoMessage() {}
func (x *SubnetworkId) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubnetworkId.ProtoReflect.Descriptor instead.
func (*SubnetworkId) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{3}
}
func (x *SubnetworkId) GetBytes() []byte {
if x != nil {
return x.Bytes
}
return nil
}
type TransactionMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
Inputs []*TransactionInput `protobuf:"bytes,2,rep,name=inputs,proto3" json:"inputs,omitempty"`
Outputs []*TransactionOutput `protobuf:"bytes,3,rep,name=outputs,proto3" json:"outputs,omitempty"`
LockTime uint64 `protobuf:"varint,4,opt,name=lockTime,proto3" json:"lockTime,omitempty"`
SubnetworkId *SubnetworkId `protobuf:"bytes,5,opt,name=subnetworkId,proto3" json:"subnetworkId,omitempty"`
Gas uint64 `protobuf:"varint,6,opt,name=gas,proto3" json:"gas,omitempty"`
Payload []byte `protobuf:"bytes,8,opt,name=payload,proto3" json:"payload,omitempty"`
}
func (x *TransactionMessage) Reset() {
*x = TransactionMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionMessage) ProtoMessage() {}
func (x *TransactionMessage) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionMessage.ProtoReflect.Descriptor instead.
func (*TransactionMessage) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{4}
}
func (x *TransactionMessage) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *TransactionMessage) GetInputs() []*TransactionInput {
if x != nil {
return x.Inputs
}
return nil
}
func (x *TransactionMessage) GetOutputs() []*TransactionOutput {
if x != nil {
return x.Outputs
}
return nil
}
func (x *TransactionMessage) GetLockTime() uint64 {
if x != nil {
return x.LockTime
}
return 0
}
func (x *TransactionMessage) GetSubnetworkId() *SubnetworkId {
if x != nil {
return x.SubnetworkId
}
return nil
}
func (x *TransactionMessage) GetGas() uint64 {
if x != nil {
return x.Gas
}
return 0
}
func (x *TransactionMessage) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
type TransactionInput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PreviousOutpoint *Outpoint `protobuf:"bytes,1,opt,name=previousOutpoint,proto3" json:"previousOutpoint,omitempty"`
SignatureScript []byte `protobuf:"bytes,2,opt,name=signatureScript,proto3" json:"signatureScript,omitempty"`
Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"`
}
func (x *TransactionInput) Reset() {
*x = TransactionInput{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionInput) ProtoMessage() {}
func (x *TransactionInput) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionInput.ProtoReflect.Descriptor instead.
func (*TransactionInput) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{5}
}
func (x *TransactionInput) GetPreviousOutpoint() *Outpoint {
if x != nil {
return x.PreviousOutpoint
}
return nil
}
func (x *TransactionInput) GetSignatureScript() []byte {
if x != nil {
return x.SignatureScript
}
return nil
}
func (x *TransactionInput) GetSequence() uint64 {
if x != nil {
return x.Sequence
}
return 0
}
type Outpoint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TransactionId *TransactionId `protobuf:"bytes,1,opt,name=transactionId,proto3" json:"transactionId,omitempty"`
Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"`
}
func (x *Outpoint) Reset() {
*x = Outpoint{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Outpoint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Outpoint) ProtoMessage() {}
func (x *Outpoint) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Outpoint.ProtoReflect.Descriptor instead.
func (*Outpoint) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{6}
}
func (x *Outpoint) GetTransactionId() *TransactionId {
if x != nil {
return x.TransactionId
}
return nil
}
func (x *Outpoint) GetIndex() uint32 {
if x != nil {
return x.Index
}
return 0
}
type TransactionId struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"`
}
func (x *TransactionId) Reset() {
*x = TransactionId{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionId) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionId) ProtoMessage() {}
func (x *TransactionId) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionId.ProtoReflect.Descriptor instead.
func (*TransactionId) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{7}
}
func (x *TransactionId) GetBytes() []byte {
if x != nil {
return x.Bytes
}
return nil
}
type ScriptPublicKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Script []byte `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`
Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
}
func (x *ScriptPublicKey) Reset() {
*x = ScriptPublicKey{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ScriptPublicKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ScriptPublicKey) ProtoMessage() {}
func (x *ScriptPublicKey) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ScriptPublicKey.ProtoReflect.Descriptor instead.
func (*ScriptPublicKey) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{8}
}
func (x *ScriptPublicKey) GetScript() []byte {
if x != nil {
return x.Script
}
return nil
}
func (x *ScriptPublicKey) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
type TransactionOutput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
ScriptPublicKey *ScriptPublicKey `protobuf:"bytes,2,opt,name=scriptPublicKey,proto3" json:"scriptPublicKey,omitempty"`
}
func (x *TransactionOutput) Reset() {
*x = TransactionOutput{}
if protoimpl.UnsafeEnabled {
mi := &file_wallet_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionOutput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionOutput) ProtoMessage() {}
func (x *TransactionOutput) ProtoReflect() protoreflect.Message {
mi := &file_wallet_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionOutput.ProtoReflect.Descriptor instead.
func (*TransactionOutput) Descriptor() ([]byte, []int) {
return file_wallet_proto_rawDescGZIP(), []int{9}
}
func (x *TransactionOutput) GetValue() uint64 {
if x != nil {
return x.Value
}
return 0
}
func (x *TransactionOutput) GetScriptPublicKey() *ScriptPublicKey {
if x != nil {
return x.ScriptPublicKey
}
return nil
}
var File_wallet_proto protoreflect.FileDescriptor
var file_wallet_proto_rawDesc = []byte{
0x0a, 0x0c, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x22, 0xb4, 0x01, 0x0a, 0x1a, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79,
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x36, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x02, 0x74, 0x78, 0x12, 0x5e, 0x0a, 0x15, 0x70, 0x61, 0x72,
0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75,
0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61,
0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x52, 0x15, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67,
0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x22, 0x8c, 0x02, 0x0a, 0x14, 0x50, 0x61,
0x72, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x53, 0x63, 0x72, 0x69,
0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d,
0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x45, 0x0a, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x4f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x76, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2c, 0x0a,
0x11, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75,
0x6d, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x5b, 0x0a, 0x14, 0x70,
0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x61,
0x69, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50,
0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x61,
0x69, 0x72, 0x52, 0x14, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74,
0x75, 0x72, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x22, 0x4b, 0x0a, 0x13, 0x50, 0x75, 0x62, 0x4b,
0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12,
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61,
0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xbb, 0x02, 0x0a, 0x12,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x06,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c,
0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6c,
0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x52,
0x0c, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x10, 0x0a,
0x03, 0x67, 0x61, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x67, 0x61, 0x73, 0x12,
0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x10, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x48,
0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x75,
0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73,
0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x63, 0x72, 0x69,
0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x69,
0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0d, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69,
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x64, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x25, 0x0a, 0x0d, 0x54, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79,
0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73,
0x22, 0x43, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x12, 0x4d, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53,
0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42,
0x5c, 0x5a, 0x5a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61,
0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d,
0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x6c, 0x69,
0x62, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_wallet_proto_rawDescOnce sync.Once
file_wallet_proto_rawDescData = file_wallet_proto_rawDesc
)
func file_wallet_proto_rawDescGZIP() []byte {
file_wallet_proto_rawDescOnce.Do(func() {
file_wallet_proto_rawDescData = protoimpl.X.CompressGZIP(file_wallet_proto_rawDescData)
})
return file_wallet_proto_rawDescData
}
var file_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_wallet_proto_goTypes = []interface{}{
(*PartiallySignedTransaction)(nil), // 0: protoserialization.PartiallySignedTransaction
(*PartiallySignedInput)(nil), // 1: protoserialization.PartiallySignedInput
(*PubKeySignaturePair)(nil), // 2: protoserialization.PubKeySignaturePair
(*SubnetworkId)(nil), // 3: protoserialization.SubnetworkId
(*TransactionMessage)(nil), // 4: protoserialization.TransactionMessage
(*TransactionInput)(nil), // 5: protoserialization.TransactionInput
(*Outpoint)(nil), // 6: protoserialization.Outpoint
(*TransactionId)(nil), // 7: protoserialization.TransactionId
(*ScriptPublicKey)(nil), // 8: protoserialization.ScriptPublicKey
(*TransactionOutput)(nil), // 9: protoserialization.TransactionOutput
}
var file_wallet_proto_depIdxs = []int32{
4, // 0: protoserialization.PartiallySignedTransaction.tx:type_name -> protoserialization.TransactionMessage
1, // 1: protoserialization.PartiallySignedTransaction.partiallySignedInputs:type_name -> protoserialization.PartiallySignedInput
9, // 2: protoserialization.PartiallySignedInput.prevOutput:type_name -> protoserialization.TransactionOutput
2, // 3: protoserialization.PartiallySignedInput.pubKeySignaturePairs:type_name -> protoserialization.PubKeySignaturePair
5, // 4: protoserialization.TransactionMessage.inputs:type_name -> protoserialization.TransactionInput
9, // 5: protoserialization.TransactionMessage.outputs:type_name -> protoserialization.TransactionOutput
3, // 6: protoserialization.TransactionMessage.subnetworkId:type_name -> protoserialization.SubnetworkId
6, // 7: protoserialization.TransactionInput.previousOutpoint:type_name -> protoserialization.Outpoint
7, // 8: protoserialization.Outpoint.transactionId:type_name -> protoserialization.TransactionId
8, // 9: protoserialization.TransactionOutput.scriptPublicKey:type_name -> protoserialization.ScriptPublicKey
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_wallet_proto_init() }
func file_wallet_proto_init() {
if File_wallet_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_wallet_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PartiallySignedTransaction); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PartiallySignedInput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PubKeySignaturePair); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubnetworkId); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionInput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Outpoint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionId); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ScriptPublicKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wallet_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionOutput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_wallet_proto_rawDesc,
NumEnums: 0,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_wallet_proto_goTypes,
DependencyIndexes: file_wallet_proto_depIdxs,
MessageInfos: file_wallet_proto_msgTypes,
}.Build()
File_wallet_proto = out.File
file_wallet_proto_rawDesc = nil
file_wallet_proto_goTypes = nil
file_wallet_proto_depIdxs = nil
}

View File

@@ -0,0 +1,59 @@
syntax = "proto3";
package protoserialization;
option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization/protoserialization";
message PartiallySignedTransaction{
TransactionMessage tx = 1;
repeated PartiallySignedInput partiallySignedInputs = 2;
}
message PartiallySignedInput{
bytes redeemScript = 1;
TransactionOutput prevOutput = 2;
uint32 minimumSignatures = 3;
repeated PubKeySignaturePair pubKeySignaturePairs = 4;
}
message PubKeySignaturePair{
bytes pubKey = 1;
bytes signature = 2;
}
message SubnetworkId{
bytes bytes = 1;
}
message TransactionMessage{
uint32 version = 1;
repeated TransactionInput inputs = 2;
repeated TransactionOutput outputs = 3;
uint64 lockTime = 4;
SubnetworkId subnetworkId = 5;
uint64 gas = 6;
bytes payload = 8;
}
message TransactionInput{
Outpoint previousOutpoint = 1;
bytes signatureScript = 2;
uint64 sequence = 3;
}
message Outpoint{
TransactionId transactionId = 1;
uint32 index = 2;
}
message TransactionId{
bytes bytes = 1;
}
message ScriptPublicKey {
bytes script = 1;
uint32 version = 2;
}
message TransactionOutput{
uint64 value = 1;
ScriptPublicKey scriptPublicKey = 2;
}

View File

@@ -0,0 +1,273 @@
package serialization
import (
"github.com/golang/protobuf/proto"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization/protoserialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/pkg/errors"
"math"
)
// PartiallySignedTransaction is a type that is intended
// to be transferred between multiple parties so each
// party will be able to sign the transaction before
// it's fully signed.
type PartiallySignedTransaction struct {
Tx *externalapi.DomainTransaction
PartiallySignedInputs []*PartiallySignedInput
}
// PartiallySignedInput represents an input signed
// only by some of the relevant parties.
type PartiallySignedInput struct {
RedeeemScript []byte
PrevOutput *externalapi.DomainTransactionOutput
MinimumSignatures uint32
PubKeySignaturePairs []*PubKeySignaturePair
}
// PubKeySignaturePair is a pair of public key and (potentially) its associated signature
type PubKeySignaturePair struct {
PubKey []byte
Signature []byte
}
// DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction.
func DeserializePartiallySignedTransaction(serializedPartiallySignedTransaction []byte) (*PartiallySignedTransaction, error) {
protoPartiallySignedTransaction := &protoserialization.PartiallySignedTransaction{}
err := proto.Unmarshal(serializedPartiallySignedTransaction, protoPartiallySignedTransaction)
if err != nil {
return nil, err
}
return partiallySignedTransactionFromProto(protoPartiallySignedTransaction)
}
// SerializePartiallySignedTransaction serializes a PartiallySignedTransaction.
func SerializePartiallySignedTransaction(partiallySignedTransaction *PartiallySignedTransaction) ([]byte, error) {
return proto.Marshal(partiallySignedTransactionToProto(partiallySignedTransaction))
}
func partiallySignedTransactionFromProto(protoPartiallySignedTransaction *protoserialization.PartiallySignedTransaction) (*PartiallySignedTransaction, error) {
tx, err := transactionFromProto(protoPartiallySignedTransaction.Tx)
if err != nil {
return nil, err
}
inputs := make([]*PartiallySignedInput, len(protoPartiallySignedTransaction.PartiallySignedInputs))
for i, protoInput := range protoPartiallySignedTransaction.PartiallySignedInputs {
inputs[i], err = partiallySignedInputFromProto(protoInput)
if err != nil {
return nil, err
}
}
return &PartiallySignedTransaction{
Tx: tx,
PartiallySignedInputs: inputs,
}, nil
}
func partiallySignedTransactionToProto(partiallySignedTransaction *PartiallySignedTransaction) *protoserialization.PartiallySignedTransaction {
protoInputs := make([]*protoserialization.PartiallySignedInput, len(partiallySignedTransaction.PartiallySignedInputs))
for i, input := range partiallySignedTransaction.PartiallySignedInputs {
protoInputs[i] = partiallySignedInputToProto(input)
}
return &protoserialization.PartiallySignedTransaction{
Tx: transactionToProto(partiallySignedTransaction.Tx),
PartiallySignedInputs: protoInputs,
}
}
func partiallySignedInputFromProto(protoPartiallySignedInput *protoserialization.PartiallySignedInput) (*PartiallySignedInput, error) {
output, err := transactionOutputFromProto(protoPartiallySignedInput.PrevOutput)
if err != nil {
return nil, err
}
pubKeySignaturePairs := make([]*PubKeySignaturePair, len(protoPartiallySignedInput.PubKeySignaturePairs))
for i, protoPair := range protoPartiallySignedInput.PubKeySignaturePairs {
pubKeySignaturePairs[i] = pubKeySignaturePairFromProto(protoPair)
}
return &PartiallySignedInput{
RedeeemScript: protoPartiallySignedInput.RedeemScript,
PrevOutput: output,
MinimumSignatures: protoPartiallySignedInput.MinimumSignatures,
PubKeySignaturePairs: pubKeySignaturePairs,
}, nil
}
func partiallySignedInputToProto(partiallySignedInput *PartiallySignedInput) *protoserialization.PartiallySignedInput {
protoPairs := make([]*protoserialization.PubKeySignaturePair, len(partiallySignedInput.PubKeySignaturePairs))
for i, pair := range partiallySignedInput.PubKeySignaturePairs {
protoPairs[i] = pubKeySignaturePairToProto(pair)
}
return &protoserialization.PartiallySignedInput{
RedeemScript: partiallySignedInput.RedeeemScript,
PrevOutput: transactionOutputToProto(partiallySignedInput.PrevOutput),
MinimumSignatures: partiallySignedInput.MinimumSignatures,
PubKeySignaturePairs: protoPairs,
}
}
func pubKeySignaturePairFromProto(protoPubKeySignaturePair *protoserialization.PubKeySignaturePair) *PubKeySignaturePair {
return &PubKeySignaturePair{
PubKey: protoPubKeySignaturePair.PubKey,
Signature: protoPubKeySignaturePair.Signature,
}
}
func pubKeySignaturePairToProto(pubKeySignaturePair *PubKeySignaturePair) *protoserialization.PubKeySignaturePair {
return &protoserialization.PubKeySignaturePair{
PubKey: pubKeySignaturePair.PubKey,
Signature: pubKeySignaturePair.Signature,
}
}
func transactionFromProto(protoTransaction *protoserialization.TransactionMessage) (*externalapi.DomainTransaction, error) {
if protoTransaction.Version > math.MaxUint16 {
return nil, errors.Errorf("protoTransaction.Version is %d and is too big to be a uint16", protoTransaction.Version)
}
inputs := make([]*externalapi.DomainTransactionInput, len(protoTransaction.Inputs))
for i, protoInput := range protoTransaction.Inputs {
var err error
inputs[i], err = transactionInputFromProto(protoInput)
if err != nil {
return nil, err
}
}
outputs := make([]*externalapi.DomainTransactionOutput, len(protoTransaction.Outputs))
for i, protoOutput := range protoTransaction.Outputs {
var err error
outputs[i], err = transactionOutputFromProto(protoOutput)
if err != nil {
return nil, err
}
}
subnetworkID, err := subnetworks.FromBytes(protoTransaction.SubnetworkId.Bytes)
if err != nil {
return nil, err
}
return &externalapi.DomainTransaction{
Version: uint16(protoTransaction.Version),
Inputs: inputs,
Outputs: outputs,
LockTime: protoTransaction.LockTime,
SubnetworkID: *subnetworkID,
Gas: protoTransaction.Gas,
Payload: protoTransaction.Payload,
}, nil
}
func transactionToProto(tx *externalapi.DomainTransaction) *protoserialization.TransactionMessage {
protoInputs := make([]*protoserialization.TransactionInput, len(tx.Inputs))
for i, input := range tx.Inputs {
protoInputs[i] = transactionInputToProto(input)
}
protoOutputs := make([]*protoserialization.TransactionOutput, len(tx.Outputs))
for i, output := range tx.Outputs {
protoOutputs[i] = transactionOutputToProto(output)
}
return &protoserialization.TransactionMessage{
Version: uint32(tx.Version),
Inputs: protoInputs,
Outputs: protoOutputs,
LockTime: tx.LockTime,
SubnetworkId: &protoserialization.SubnetworkId{Bytes: tx.SubnetworkID[:]},
Gas: tx.Gas,
Payload: tx.Payload,
}
}
func transactionInputFromProto(protoInput *protoserialization.TransactionInput) (*externalapi.DomainTransactionInput, error) {
outpoint, err := outpointFromProto(protoInput.PreviousOutpoint)
if err != nil {
return nil, err
}
return &externalapi.DomainTransactionInput{
PreviousOutpoint: *outpoint,
SignatureScript: protoInput.SignatureScript,
Sequence: protoInput.Sequence,
}, nil
}
func transactionInputToProto(input *externalapi.DomainTransactionInput) *protoserialization.TransactionInput {
return &protoserialization.TransactionInput{
PreviousOutpoint: outpointToProto(&input.PreviousOutpoint),
SignatureScript: input.SignatureScript,
Sequence: input.Sequence,
}
}
func outpointFromProto(protoOutpoint *protoserialization.Outpoint) (*externalapi.DomainOutpoint, error) {
txID, err := transactionIDFromProto(protoOutpoint.TransactionId)
if err != nil {
return nil, err
}
return &externalapi.DomainOutpoint{
TransactionID: *txID,
Index: protoOutpoint.Index,
}, nil
}
func outpointToProto(outpoint *externalapi.DomainOutpoint) *protoserialization.Outpoint {
return &protoserialization.Outpoint{
TransactionId: &protoserialization.TransactionId{Bytes: outpoint.TransactionID.ByteSlice()},
Index: outpoint.Index,
}
}
func transactionIDFromProto(protoTxID *protoserialization.TransactionId) (*externalapi.DomainTransactionID, error) {
if protoTxID == nil {
return nil, errors.Errorf("protoTxID is nil")
}
return externalapi.NewDomainTransactionIDFromByteSlice(protoTxID.Bytes)
}
func transactionOutputFromProto(protoOutput *protoserialization.TransactionOutput) (*externalapi.DomainTransactionOutput, error) {
scriptPublicKey, err := scriptPublicKeyFromProto(protoOutput.ScriptPublicKey)
if err != nil {
return nil, err
}
return &externalapi.DomainTransactionOutput{
Value: protoOutput.Value,
ScriptPublicKey: scriptPublicKey,
}, nil
}
func transactionOutputToProto(output *externalapi.DomainTransactionOutput) *protoserialization.TransactionOutput {
return &protoserialization.TransactionOutput{
Value: output.Value,
ScriptPublicKey: scriptPublicKeyToProto(output.ScriptPublicKey),
}
}
func scriptPublicKeyFromProto(protoScriptPublicKey *protoserialization.ScriptPublicKey) (*externalapi.ScriptPublicKey, error) {
if protoScriptPublicKey.Version > math.MaxUint16 {
return nil, errors.Errorf("protoOutput.ScriptPublicKey.Version is %d and is too big to be a uint16", protoScriptPublicKey.Version)
}
return &externalapi.ScriptPublicKey{
Script: protoScriptPublicKey.Script,
Version: uint16(protoScriptPublicKey.Version),
}, nil
}
func scriptPublicKeyToProto(scriptPublicKey *externalapi.ScriptPublicKey) *protoserialization.ScriptPublicKey {
return &protoserialization.ScriptPublicKey{
Script: scriptPublicKey.Script,
Version: uint32(scriptPublicKey.Version),
}
}

View File

@@ -0,0 +1,146 @@
package libkaspawallet
import (
"bytes"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/pkg/errors"
)
type signer interface {
rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error)
serializedPublicKey() ([]byte, error)
}
type schnorrSigner secp256k1.SchnorrKeyPair
func (s *schnorrSigner) rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
return txscript.RawTxInSignature(tx, idx, hashType, (*secp256k1.SchnorrKeyPair)(s), sighashReusedValues)
}
func (s *schnorrSigner) serializedPublicKey() ([]byte, error) {
publicKey, err := (*secp256k1.SchnorrKeyPair)(s).SchnorrPublicKey()
if err != nil {
return nil, err
}
serializedPublicKey, err := publicKey.Serialize()
if err != nil {
return nil, err
}
return serializedPublicKey[:], nil
}
type ecdsaSigner secp256k1.ECDSAPrivateKey
func (e *ecdsaSigner) rawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
return txscript.RawTxInSignatureECDSA(tx, idx, hashType, (*secp256k1.ECDSAPrivateKey)(e), sighashReusedValues)
}
func (e *ecdsaSigner) serializedPublicKey() ([]byte, error) {
publicKey, err := (*secp256k1.ECDSAPrivateKey)(e).ECDSAPublicKey()
if err != nil {
return nil, err
}
serializedPublicKey, err := publicKey.Serialize()
if err != nil {
return nil, err
}
return serializedPublicKey[:], nil
}
func deserializeECDSAPrivateKey(privateKey []byte, ecdsa bool) (signer, error) {
if ecdsa {
keyPair, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(privateKey)
if err != nil {
return nil, errors.Wrap(err, "Error deserializing private key")
}
return (*ecdsaSigner)(keyPair), nil
}
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKey)
if err != nil {
return nil, errors.Wrap(err, "Error deserializing private key")
}
return (*schnorrSigner)(keyPair), nil
}
// Sign signs the transaction with the given private keys
func Sign(privateKeys [][]byte, serializedPSTx []byte, ecdsa bool) ([]byte, error) {
keyPairs := make([]signer, len(privateKeys))
for i, privateKey := range privateKeys {
var err error
keyPairs[i], err = deserializeECDSAPrivateKey(privateKey, ecdsa)
if err != nil {
return nil, errors.Wrap(err, "Error deserializing private key")
}
}
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(serializedPSTx)
if err != nil {
return nil, err
}
for _, keyPair := range keyPairs {
err = sign(keyPair, partiallySignedTransaction)
if err != nil {
return nil, err
}
}
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
}
func sign(keyPair signer, psTx *serialization.PartiallySignedTransaction) error {
if isTransactionFullySigned(psTx) {
return nil
}
serializedPublicKey, err := keyPair.serializedPublicKey()
if err != nil {
return err
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
prevOut := partiallySignedInput.PrevOutput
psTx.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(
prevOut.Value,
prevOut.ScriptPublicKey,
false, // This is a fake value, because it's irrelevant for the signature
0, // This is a fake value, because it's irrelevant for the signature
)
}
signed := false
for i, partiallySignedInput := range psTx.PartiallySignedInputs {
for _, pair := range partiallySignedInput.PubKeySignaturePairs {
if bytes.Equal(pair.PubKey, serializedPublicKey[:]) {
pair.Signature, err = keyPair.rawTxInSignature(psTx.Tx, i, consensushashing.SigHashAll, sighashReusedValues)
if err != nil {
return err
}
signed = true
}
}
}
if !signed {
return errors.Errorf("Public key doesn't match any of the transaction public keys")
}
return nil
}

View File

@@ -0,0 +1,206 @@
package libkaspawallet
import (
"bytes"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"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/util"
"github.com/pkg/errors"
"sort"
)
// Payment contains a recipient payment details
type Payment struct {
Address util.Address
Amount uint64
}
func sortPublicKeys(publicKeys [][]byte) {
sort.Slice(publicKeys, func(i, j int) bool {
return bytes.Compare(publicKeys[i], publicKeys[j]) < 0
})
}
// CreateUnsignedTransaction creates an unsigned transaction
func CreateUnsignedTransaction(
pubKeys [][]byte,
minimumSignatures uint32,
ecdsa bool,
payments []*Payment,
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair) ([]byte, error) {
sortPublicKeys(pubKeys)
unsignedTransaction, err := createUnsignedTransaction(pubKeys, minimumSignatures, ecdsa, payments, selectedUTXOs)
if err != nil {
return nil, err
}
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
}
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)))
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, ecdsa)
if err != nil {
return nil, err
}
}
inputs := make([]*externalapi.DomainTransactionInput, len(selectedUTXOs))
partiallySignedInputs := make([]*serialization.PartiallySignedInput, len(selectedUTXOs))
for i, utxo := range selectedUTXOs {
emptyPubKeySignaturePairs := make([]*serialization.PubKeySignaturePair, len(pubKeys))
for i, pubKey := range pubKeys {
emptyPubKeySignaturePairs[i] = &serialization.PubKeySignaturePair{
PubKey: pubKey,
}
}
inputs[i] = &externalapi.DomainTransactionInput{PreviousOutpoint: *utxo.Outpoint}
partiallySignedInputs[i] = &serialization.PartiallySignedInput{
RedeeemScript: redeemScript,
PrevOutput: &externalapi.DomainTransactionOutput{
Value: utxo.UTXOEntry.Amount(),
ScriptPublicKey: utxo.UTXOEntry.ScriptPublicKey(),
},
MinimumSignatures: minimumSignatures,
PubKeySignaturePairs: emptyPubKeySignaturePairs,
}
}
outputs := make([]*externalapi.DomainTransactionOutput, len(payments))
for i, payment := range payments {
scriptPublicKey, err := txscript.PayToAddrScript(payment.Address)
if err != nil {
return nil, err
}
outputs[i] = &externalapi.DomainTransactionOutput{
Value: payment.Amount,
ScriptPublicKey: scriptPublicKey,
}
}
domainTransaction := &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: inputs,
Outputs: outputs,
LockTime: 0,
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
Payload: nil,
}
return &serialization.PartiallySignedTransaction{
Tx: domainTransaction,
PartiallySignedInputs: partiallySignedInputs,
}, nil
}
// IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
func IsTransactionFullySigned(psTxBytes []byte) (bool, error) {
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(psTxBytes)
if err != nil {
return false, err
}
return isTransactionFullySigned(partiallySignedTransaction), nil
}
func isTransactionFullySigned(psTx *serialization.PartiallySignedTransaction) bool {
for _, input := range psTx.PartiallySignedInputs {
numSignatures := 0
for _, pair := range input.PubKeySignaturePairs {
if pair.Signature != nil {
numSignatures++
}
}
if uint32(numSignatures) < input.MinimumSignatures {
return false
}
}
return true
}
// ExtractTransaction extracts a domain transaction from partially signed transaction after all of the
// relevant parties have signed it.
func ExtractTransaction(psTxBytes []byte) (*externalapi.DomainTransaction, error) {
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(psTxBytes)
if err != nil {
return nil, err
}
return extractTransaction(partiallySignedTransaction)
}
func extractTransaction(psTx *serialization.PartiallySignedTransaction) (*externalapi.DomainTransaction, error) {
for i, input := range psTx.PartiallySignedInputs {
isMultisig := input.RedeeemScript != nil
scriptBuilder := txscript.NewScriptBuilder()
if isMultisig {
signatureCount := 0
for _, pair := range input.PubKeySignaturePairs {
if pair.Signature != nil {
scriptBuilder.AddData(pair.Signature)
signatureCount++
}
}
if uint32(signatureCount) < input.MinimumSignatures {
return nil, errors.Errorf("missing %d signatures", input.MinimumSignatures-uint32(signatureCount))
}
scriptBuilder.AddData(input.RedeeemScript)
sigScript, err := scriptBuilder.Script()
if err != nil {
return nil, err
}
psTx.Tx.Inputs[i].SignatureScript = sigScript
} else {
if len(input.PubKeySignaturePairs) > 1 {
return nil, errors.Errorf("Cannot sign on P2PK when len(input.PubKeySignaturePairs) > 1")
}
if input.PubKeySignaturePairs[0].Signature == nil {
return nil, errors.Errorf("missing signature")
}
sigScript, err := txscript.NewScriptBuilder().
AddData(input.PubKeySignaturePairs[0].Signature).
Script()
if err != nil {
return nil, err
}
psTx.Tx.Inputs[i].SignatureScript = sigScript
}
}
return psTx.Tx, nil
}

View File

@@ -0,0 +1,291 @@
package libkaspawallet_test
import (
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"strings"
"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) {
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("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(ecdsa)
if err != nil {
t.Fatalf("CreateKeyPair: %+v", err)
}
}
const minimumSignatures = 2
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, ecdsa)
if err != nil {
t.Fatalf("Address: %+v", err)
}
if _, ok := address.(*util.AddressScriptHash); !ok {
t.Fatalf("The address is of unexpected type")
}
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
t.Fatalf("PayToAddrScript: %+v", err)
}
coinbaseData := &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
ExtraData: nil,
}
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
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,
}
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
t.Fatalf("Transaction wasn't accepted in the DAG")
}
})
})
}
func TestP2PK(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
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("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(ecdsa)
if err != nil {
t.Fatalf("CreateKeyPair: %+v", err)
}
}
const minimumSignatures = 1
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, ecdsa)
if err != nil {
t.Fatalf("Address: %+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")
}
}
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
t.Fatalf("PayToAddrScript: %+v", err)
}
coinbaseData := &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
ExtraData: nil,
}
fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
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,
}
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(addedUTXO) {
t.Fatalf("Transaction wasn't accepted in the DAG")
}
})
})
}

33
cmd/kaspawallet/main.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import "github.com/pkg/errors"
func main() {
subCmd, config := parseCommandLine()
var err error
switch subCmd {
case createSubCmd:
err = create(config.(*createConfig))
case balanceSubCmd:
err = balance(config.(*balanceConfig))
case sendSubCmd:
err = send(config.(*sendConfig))
case createUnsignedTransactionSubCmd:
err = createUnsignedTransaction(config.(*createUnsignedTransactionConfig))
case signSubCmd:
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)
}
if err != nil {
printErrorAndExit(err)
}
}

170
cmd/kaspawallet/send.go Normal file
View File

@@ -0,0 +1,170 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
utxopkg "github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func send(conf *sendConfig) error {
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
if err != nil {
return err
}
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.ActiveNetParams.Prefix)
if err != nil {
return err
}
fromAddress, err := libkaspawallet.Address(conf.NetParams(), keysFile.PublicKeys, keysFile.MinimumSignatures, keysFile.ECDSA)
if err != nil {
return err
}
client, err := connectToRPC(conf.NetParams(), conf.RPCServer)
if err != nil {
return err
}
utxos, err := fetchSpendableUTXOs(conf.NetParams(), client, fromAddress.String())
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
const feePerInput = 1000
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, sendAmountSompi, feePerInput)
if err != nil {
return err
}
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
}
privateKeys, err := keysFile.DecryptPrivateKeys()
if err != nil {
return err
}
updatedPSTx, err := libkaspawallet.Sign(privateKeys, psTx, keysFile.ECDSA)
if err != nil {
return err
}
tx, err := libkaspawallet.ExtractTransaction(updatedPSTx)
if err != nil {
return err
}
transactionID, err := sendTransaction(client, tx)
if err != nil {
return err
}
fmt.Println("Transaction was sent successfully")
fmt.Printf("Transaction ID: \t%s\n", transactionID)
return nil
}
func fetchSpendableUTXOs(params *dagconfig.Params, client *rpcclient.RPCClient, address string) ([]*appmessage.UTXOsByAddressesEntry, error) {
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{address})
if err != nil {
return nil, err
}
virtualSelectedParentBlueScoreResponse, err := client.GetVirtualSelectedParentBlueScore()
if err != nil {
return nil, err
}
virtualSelectedParentBlueScore := virtualSelectedParentBlueScoreResponse.BlueScore
spendableUTXOs := make([]*appmessage.UTXOsByAddressesEntry, 0)
for _, entry := range getUTXOsByAddressesResponse.Entries {
if !isUTXOSpendable(entry, virtualSelectedParentBlueScore, params.BlockCoinbaseMaturity) {
continue
}
spendableUTXOs = append(spendableUTXOs, entry)
}
return spendableUTXOs, nil
}
func selectUTXOs(utxos []*appmessage.UTXOsByAddressesEntry, spendAmount uint64, feePerInput uint64) (
selectedUTXOs []*externalapi.OutpointAndUTXOEntryPair, changeSompi uint64, err error) {
selectedUTXOs = []*externalapi.OutpointAndUTXOEntryPair{}
totalValue := uint64(0)
for _, utxo := range utxos {
txID, err := transactionid.FromString(utxo.Outpoint.TransactionID)
if err != nil {
return nil, 0, err
}
rpcUTXOEntry := utxo.UTXOEntry
scriptPublicKeyScript, err := hex.DecodeString(rpcUTXOEntry.ScriptPublicKey.Script)
if err != nil {
return nil, 0, err
}
scriptPublicKey := &externalapi.ScriptPublicKey{
Script: scriptPublicKeyScript,
Version: rpcUTXOEntry.ScriptPublicKey.Version,
}
utxoEntry := utxopkg.NewUTXOEntry(rpcUTXOEntry.Amount, scriptPublicKey, rpcUTXOEntry.IsCoinbase, rpcUTXOEntry.BlockDAAScore)
selectedUTXOs = append(selectedUTXOs, &externalapi.OutpointAndUTXOEntryPair{
Outpoint: &externalapi.DomainOutpoint{
TransactionID: *txID,
Index: utxo.Outpoint.Index,
},
UTXOEntry: utxoEntry,
})
totalValue += utxo.UTXOEntry.Amount
fee := feePerInput * uint64(len(selectedUTXOs))
totalSpend := spendAmount + fee
if totalValue >= totalSpend {
break
}
}
fee := feePerInput * uint64(len(selectedUTXOs))
totalSpend := spendAmount + fee
if totalValue < totalSpend {
return nil, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
float64(totalSpend)/util.SompiPerKaspa, float64(totalValue)/util.SompiPerKaspa)
}
return selectedUTXOs, totalValue - totalSpend, nil
}
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx))
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction")
}
return submitTransactionResponse.TransactionID, nil
}

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

44
cmd/kaspawallet/sign.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
)
func sign(conf *signConfig) error {
keysFile, err := keys.ReadKeysFile(conf.KeysFile)
if err != nil {
return err
}
psTxBytes, err := hex.DecodeString(conf.Transaction)
if err != nil {
return err
}
privateKeys, err := keysFile.DecryptPrivateKeys()
if err != nil {
return err
}
updatedPSTxBytes, err := libkaspawallet.Sign(privateKeys, psTxBytes, keysFile.ECDSA)
if err != nil {
return err
}
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPSTxBytes)
if err != nil {
return err
}
if isFullySigned {
fmt.Println("The transaction is signed and ready to broadcast")
} else {
fmt.Println("Successfully signed transaction")
}
fmt.Printf("Transaction: %x\n", updatedPSTxBytes)
return nil
}

View File

@@ -1,48 +0,0 @@
WALLET
======
## IMPORTANT:
### This software is for TESTING ONLY. Do NOT use it for handling real money.
`wallet` is a simple, no-frills wallet software operated via the command line.\
It is capable of generating wallet key-pairs, printing a wallet's current balance, and sending simple transactions.
## Requirements
Go 1.16 or later.
## Installation
#### Build from Source
- Install Go according to the installation instructions here:
http://golang.org/doc/install
- Ensure Go was installed properly and is a supported version:
```bash
$ go version
```
- Run the following commands to obtain and install kaspad including all dependencies:
```bash
$ git clone https://github.com/kaspanet/kaspad
$ cd kaspad/cmd/wallet
$ go install .
```
- Wallet should now be installed in `$(go env GOPATH)/bin`. If you did
not already add the bin directory to your system path during Go installation,
you are encouraged to do so now.
Usage
-----
* Create a new wallet key-pair: `wallet create --testnet`
* Print a wallet's current balance:
`wallet balance --testnet --address=kaspatest:000000000000000000000000000000000000000000`
* Send funds to another wallet:
`wallet send --testnet --private-key=0000000000000000000000000000000000000000000000000000000000000000 --send-amount=50 --to-address=kaspatest:000000000000000000000000000000000000000000`

View File

@@ -1,85 +0,0 @@
package main
import (
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"os"
"github.com/jessevdk/go-flags"
)
const (
createSubCmd = "create"
balanceSubCmd = "balance"
sendSubCmd = "send"
)
type createConfig struct {
config.NetworkFlags
}
type balanceConfig struct {
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Address string `long:"address" short:"d" description:"The public address to check the balance of" required:"true"`
config.NetworkFlags
}
type sendConfig struct {
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
PrivateKey string `long:"private-key" short:"k" description:"The private key of the sender (encoded in hex)" required:"true"`
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"`
config.NetworkFlags
}
func parseCommandLine() (subCommand string, config interface{}) {
cfg := &struct{}{}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
createConf := &createConfig{}
parser.AddCommand(createSubCmd, "Creates a new wallet",
"Creates a private key and 3 public addresses, one for each of MainNet, TestNet and DevNet", createConf)
balanceConf := &balanceConfig{}
parser.AddCommand(balanceSubCmd, "Shows the balance of a public address",
"Shows the balance for a public address in Kaspa", balanceConf)
sendConf := &sendConfig{}
parser.AddCommand(sendSubCmd, "Sends a Kaspa transaction to a public address",
"Sends a Kaspa transaction to a public address", sendConf)
_, err := parser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
os.Exit(0)
} else {
os.Exit(1)
}
return "", nil
}
switch parser.Command.Active.Name {
case createSubCmd:
err := createConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = createConf
case balanceSubCmd:
err := balanceConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = balanceConf
case sendSubCmd:
err := sendConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = sendConf
}
return parser.Command.Active.Name, config
}

View File

@@ -1,37 +0,0 @@
package main
import (
"fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func create(conf *createConfig) error {
privateKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return errors.Wrap(err, "Failed to generate private key")
}
fmt.Println("This is your private key, granting access to all wallet funds. Keep it safe. Use it only when sending Kaspa.")
fmt.Printf("Private key (hex):\t%s\n\n", privateKey.SerializePrivateKey())
fmt.Println("This is your public address, where money is to be sent.")
publicKey, err := privateKey.SchnorrPublicKey()
if err != nil {
return errors.Wrap(err, "Failed to generate public key")
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
return errors.Wrap(err, "Failed to serialize public key")
}
addr, err := util.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], conf.ActiveNetParams.Prefix)
if err != nil {
return errors.Wrap(err, "Failed to generate p2pkh address")
}
fmt.Printf("Address (%s):\t%s\n", conf.ActiveNetParams.Name, addr)
return nil
}

View File

@@ -1,25 +0,0 @@
package main
import (
"github.com/pkg/errors"
)
func main() {
subCmd, config := parseCommandLine()
var err error
switch subCmd {
case createSubCmd:
err = create(config.(*createConfig))
case balanceSubCmd:
err = balance(config.(*balanceConfig))
case sendSubCmd:
err = send(config.(*sendConfig))
default:
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
}
if err != nil {
printErrorAndExit(err)
}
}

View File

@@ -1,204 +0,0 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/app/appmessage"
"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/transactionid"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
const feeSompis uint64 = 1000
func send(conf *sendConfig) error {
toAddress, err := util.DecodeAddress(conf.ToAddress, conf.ActiveNetParams.Prefix)
if err != nil {
return err
}
keyPair, publicKey, err := parsePrivateKey(conf.PrivateKey)
if err != nil {
return err
}
serializedPublicKey, err := publicKey.Serialize()
if err != nil {
return err
}
fromAddress, err := util.NewAddressPubKeyHashFromPublicKey(serializedPublicKey[:], conf.ActiveNetParams.Prefix)
if err != nil {
return err
}
client, err := rpcclient.NewRPCClient(conf.RPCServer)
if err != nil {
return err
}
utxos, err := fetchSpendableUTXOs(conf, client, fromAddress.String())
if err != nil {
return err
}
sendAmountSompi := uint64(conf.SendAmount * util.SompiPerKaspa)
totalToSend := sendAmountSompi + feeSompis
selectedUTXOs, changeSompi, err := selectUTXOs(utxos, totalToSend)
if err != nil {
return err
}
rpcTransaction, err := generateTransaction(keyPair, selectedUTXOs, sendAmountSompi, changeSompi, toAddress, fromAddress)
if err != nil {
return err
}
transactionID, err := sendTransaction(client, rpcTransaction)
if err != nil {
return err
}
fmt.Println("Transaction was sent successfully")
fmt.Printf("Transaction ID: \t%s\n", transactionID)
return nil
}
func parsePrivateKey(privateKeyHex string) (*secp256k1.SchnorrKeyPair, *secp256k1.SchnorrPublicKey, error) {
privateKeyBytes, err := hex.DecodeString(privateKeyHex)
if err != nil {
return nil, nil, errors.Wrap(err, "Error parsing private key hex")
}
keyPair, err := secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "Error deserializing private key")
}
publicKey, err := keyPair.SchnorrPublicKey()
if err != nil {
return nil, nil, errors.Wrap(err, "Error generating public key")
}
return keyPair, publicKey, nil
}
func fetchSpendableUTXOs(conf *sendConfig, client *rpcclient.RPCClient, address string) ([]*appmessage.UTXOsByAddressesEntry, error) {
getUTXOsByAddressesResponse, err := client.GetUTXOsByAddresses([]string{address})
if err != nil {
return nil, err
}
virtualSelectedParentBlueScoreResponse, err := client.GetVirtualSelectedParentBlueScore()
if err != nil {
return nil, err
}
virtualSelectedParentBlueScore := virtualSelectedParentBlueScoreResponse.BlueScore
spendableUTXOs := make([]*appmessage.UTXOsByAddressesEntry, 0)
for _, entry := range getUTXOsByAddressesResponse.Entries {
if !isUTXOSpendable(entry, virtualSelectedParentBlueScore, conf.ActiveNetParams.BlockCoinbaseMaturity) {
continue
}
spendableUTXOs = append(spendableUTXOs, entry)
}
return spendableUTXOs, nil
}
func selectUTXOs(utxos []*appmessage.UTXOsByAddressesEntry, totalToSpend uint64) (
selectedUTXOs []*appmessage.UTXOsByAddressesEntry, changeSompi uint64, err error) {
selectedUTXOs = []*appmessage.UTXOsByAddressesEntry{}
totalValue := uint64(0)
for _, utxo := range utxos {
selectedUTXOs = append(selectedUTXOs, utxo)
totalValue += utxo.UTXOEntry.Amount
if totalValue >= totalToSpend {
break
}
}
if totalValue < totalToSpend {
return nil, 0, errors.Errorf("Insufficient funds for send: %f required, while only %f available",
float64(totalToSpend)/util.SompiPerKaspa, float64(totalValue)/util.SompiPerKaspa)
}
return selectedUTXOs, totalValue - totalToSpend, nil
}
func generateTransaction(keyPair *secp256k1.SchnorrKeyPair, selectedUTXOs []*appmessage.UTXOsByAddressesEntry,
sompisToSend uint64, change uint64, toAddress util.Address,
fromAddress util.Address) (*appmessage.RPCTransaction, error) {
inputs := make([]*externalapi.DomainTransactionInput, len(selectedUTXOs))
for i, utxo := range selectedUTXOs {
outpointTransactionIDBytes, err := hex.DecodeString(utxo.Outpoint.TransactionID)
if err != nil {
return nil, err
}
outpointTransactionID, err := transactionid.FromBytes(outpointTransactionIDBytes)
if err != nil {
return nil, err
}
outpoint := externalapi.DomainOutpoint{
TransactionID: *outpointTransactionID,
Index: utxo.Outpoint.Index,
}
inputs[i] = &externalapi.DomainTransactionInput{PreviousOutpoint: outpoint}
}
toScript, err := txscript.PayToAddrScript(toAddress)
if err != nil {
return nil, err
}
mainOutput := &externalapi.DomainTransactionOutput{
Value: sompisToSend,
ScriptPublicKey: toScript,
}
fromScript, err := txscript.PayToAddrScript(fromAddress)
if err != nil {
return nil, err
}
changeOutput := &externalapi.DomainTransactionOutput{
Value: change,
ScriptPublicKey: fromScript,
}
outputs := []*externalapi.DomainTransactionOutput{mainOutput, changeOutput}
domainTransaction := &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: inputs,
Outputs: outputs,
LockTime: 0,
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
Payload: nil,
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, input := range domainTransaction.Inputs {
signatureScript, err := txscript.SignatureScript(
domainTransaction, i, consensushashing.SigHashAll, keyPair, sighashReusedValues)
if err != nil {
return nil, err
}
input.SignatureScript = signatureScript
}
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
return rpcTransaction, nil
}
func sendTransaction(client *rpcclient.RPCClient, rpcTransaction *appmessage.RPCTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransaction(rpcTransaction)
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction")
}
return submitTransactionResponse.TransactionID, nil
}

View File

@@ -166,7 +166,10 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap
return blockInfo, nil
}
func (s *consensus) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
func (s *consensus) GetBlockRelations(blockHash *externalapi.DomainHash) (
parents []*externalapi.DomainHash, selectedParent *externalapi.DomainHash,
children []*externalapi.DomainHash, err error) {
s.lock.Lock()
defer s.lock.Unlock()
@@ -174,10 +177,15 @@ func (s *consensus) GetBlockChildren(blockHash *externalapi.DomainHash) ([]*exte
blockRelation, err := s.blockRelationStore.BlockRelation(s.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, err
return nil, nil, nil, err
}
return blockRelation.Children, nil
blockGHOSTDAGData, err := s.ghostdagDataStore.Get(s.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, nil, nil, err
}
return blockRelation.Parents, blockGHOSTDAGData.SelectedParent(), blockRelation.Children, nil
}
func (s *consensus) GetBlockAcceptanceData(blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error) {

View File

@@ -9,7 +9,7 @@ type Consensus interface {
GetBlock(blockHash *DomainHash) (*DomainBlock, error)
GetBlockHeader(blockHash *DomainHash) (BlockHeader, error)
GetBlockInfo(blockHash *DomainHash) (*BlockInfo, error)
GetBlockChildren(blockHash *DomainHash) ([]*DomainHash, error)
GetBlockRelations(blockHash *DomainHash) (parents []*DomainHash, selectedParent *DomainHash, children []*DomainHash, err error)
GetBlockAcceptanceData(blockHash *DomainHash) (AcceptanceData, error)
GetHashesBetween(lowHash, highHash *DomainHash, maxBlueScoreDifference uint64) (hashes []*DomainHash, actualHighHash *DomainHash, err error)

View File

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

View File

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

View File

@@ -3,6 +3,8 @@ package blockprocessor
import (
"fmt"
"github.com/kaspanet/kaspad/util/staging"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/kaspanet/kaspad/domain/consensus/model"
@@ -117,7 +119,7 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea,
}
}
err = bp.commitAllChanges(stagingArea)
err = staging.CommitAllChanges(bp.databaseContext, stagingArea)
if err != nil {
return nil, err
}
@@ -298,20 +300,3 @@ func (bp *blockProcessor) hasValidatedHeader(stagingArea *model.StagingArea, blo
return status != externalapi.StatusInvalid, nil
}
func (bp *blockProcessor) commitAllChanges(stagingArea *model.StagingArea) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "commitAllChanges")
defer onEnd()
dbTx, err := bp.databaseContext.Begin()
if err != nil {
return err
}
err = stagingArea.Commit(dbTx)
if err != nil {
return err
}
return dbTx.Commit()
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/util/staging"
"github.com/pkg/errors"
)
@@ -70,7 +71,7 @@ func (bp *blockProcessor) validateBlock(stagingArea *model.StagingArea, block *e
stagingArea := model.NewStagingArea()
hash := consensushashing.BlockHash(block)
bp.blockStatusStore.Stage(stagingArea, hash, externalapi.StatusInvalid)
commitErr := bp.commitAllChanges(stagingArea)
commitErr := staging.CommitAllChanges(bp.databaseContext, stagingArea)
if commitErr != nil {
return commitErr
}

View File

@@ -199,7 +199,7 @@ func TestIsFinalizedTransaction(t *testing.T) {
}
checkForLockTimeAndSequence := func(lockTime, sequence uint64, shouldPass bool) {
tx, err := testutils.CreateTransaction(parentToSpend.Transactions[0])
tx, err := testutils.CreateTransaction(parentToSpend.Transactions[0], 1)
if err != nil {
t.Fatalf("Error creating tx: %+v", err)
}

View File

@@ -49,12 +49,12 @@ func TestChainedTransactions(t *testing.T) {
t.Fatalf("Error getting block1: %+v", err)
}
tx1, err := testutils.CreateTransaction(block1.Transactions[0])
tx1, err := testutils.CreateTransaction(block1.Transactions[0], 1)
if err != nil {
t.Fatalf("Error creating tx1: %+v", err)
}
chainedTx, err := testutils.CreateTransaction(tx1)
chainedTx, err := testutils.CreateTransaction(tx1, 1)
if err != nil {
t.Fatalf("Error creating chainedTx: %+v", err)
}
@@ -76,7 +76,7 @@ func TestChainedTransactions(t *testing.T) {
t.Fatalf("Error getting block2: %+v", err)
}
tx2, err := testutils.CreateTransaction(block2.Transactions[0])
tx2, err := testutils.CreateTransaction(block2.Transactions[0], 1)
if err != nil {
t.Fatalf("Error creating tx2: %+v", err)
}

View File

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

View File

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

View File

@@ -57,7 +57,7 @@ func TestUTXOCommitment(t *testing.T) {
checkBlockUTXOCommitment(t, consensus, blockCHash, "C")
// Block D:
blockDTransaction, err := testutils.CreateTransaction(
blockB.Transactions[transactionhelper.CoinbaseTransactionIndex])
blockB.Transactions[transactionhelper.CoinbaseTransactionIndex], 1)
if err != nil {
t.Fatalf("Error creating transaction: %+v", err)
}

View File

@@ -3,6 +3,8 @@ package consensusstatemanager
import (
"fmt"
"github.com/kaspanet/kaspad/util/staging"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
@@ -10,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()
@@ -50,19 +52,32 @@ func (csm *consensusStateManager) resolveBlockStatus(
for i := len(unverifiedBlocks) - 1; i >= 0; i-- {
unverifiedBlockHash := unverifiedBlocks[i]
stagingAreaForCurrentBlock := stagingArea
useSeparateStagingArea := useSeparateStagingAreasPerBlock && (i != 0)
if useSeparateStagingArea {
stagingAreaForCurrentBlock = model.NewStagingArea()
}
if selectedParentStatus == externalapi.StatusDisqualifiedFromChain {
blockStatus = externalapi.StatusDisqualifiedFromChain
} else {
blockStatus, err = csm.resolveSingleBlockStatus(stagingArea, unverifiedBlockHash)
blockStatus, err = csm.resolveSingleBlockStatus(stagingAreaForCurrentBlock, unverifiedBlockHash)
if err != nil {
return 0, err
}
}
csm.blockStatusStore.Stage(stagingArea, unverifiedBlockHash, blockStatus)
csm.blockStatusStore.Stage(stagingAreaForCurrentBlock, unverifiedBlockHash, blockStatus)
selectedParentStatus = blockStatus
log.Debugf("Block %s status resolved to `%s`, finished %d/%d of unverified blocks",
unverifiedBlockHash, blockStatus, len(unverifiedBlocks)-i, len(unverifiedBlocks))
if useSeparateStagingArea {
err := staging.CommitAllChanges(csm.databaseContext, stagingAreaForCurrentBlock)
if err != nil {
return 0, err
}
}
}
return blockStatus, nil

View File

@@ -51,11 +51,11 @@ func TestDoubleSpends(t *testing.T) {
fundingTransaction := fundingBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
// Create two transactions that spends the same output, but with different IDs
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction)
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction, 1)
if err != nil {
t.Fatalf("Error creating spendingTransaction1: %+v", err)
}
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction)
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction, 1)
if err != nil {
t.Fatalf("Error creating spendingTransaction2: %+v", 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)
}
@@ -213,7 +214,7 @@ func TestTransactionAcceptance(t *testing.T) {
fundingTransaction2 := fundingBlock3.Transactions[transactionhelper.CoinbaseTransactionIndex]
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction1)
spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction1, 1)
if err != nil {
t.Fatalf("Error creating spendingTransaction1: %+v", err)
}
@@ -223,7 +224,7 @@ func TestTransactionAcceptance(t *testing.T) {
t.Fatalf("Error getting UTXOEntry for spendingTransaction1: %s", err)
}
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction2)
spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction2, 1)
if err != nil {
t.Fatalf("Error creating spendingTransaction1: %+v", err)
}

View File

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

View File

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

View File

@@ -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 - "+

View File

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

View File

@@ -46,7 +46,7 @@ func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
defer tearDown(false)
pastMedianManager.pastMedianTimeForTest = 1
privateKey, err := secp256k1.GeneratePrivateKey()
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Fatalf("Failed to generate a private key: %v", err)
}
@@ -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 {
@@ -238,3 +238,251 @@ func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
}
})
}
func TestSigningTwoInputs(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, "TestSigningTwoInputs")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Fatalf("Failed to generate a private key: %v", err)
}
publicKey, err := privateKey.SchnorrPublicKey()
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.NewAddressPublicKey(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.SignatureScript(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")
}
})
}
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")
}
})
}

View File

@@ -21,6 +21,8 @@ type testConsensus struct {
testReachabilityManager testapi.TestReachabilityManager
testConsensusStateManager testapi.TestConsensusStateManager
testTransactionValidator testapi.TestTransactionValidator
buildBlockConsensus *consensus
}
func (tc *testConsensus) DAGParams() *dagconfig.Params {

View File

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

View File

@@ -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)
}
}
}
@@ -334,7 +456,7 @@ func signTx(b *testing.B, tx *externalapi.DomainTransaction, sigHashTypes []cons
if err != nil {
b.Fatalf("Error parsing private key hex: %+v", err)
}
keyPair, err := secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
keyPair, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKeyBytes)
if err != nil {
b.Fatalf("Error deserializing private key: %+v", err)
}

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import (
// CreateTransaction create a transaction that spends the first output of provided transaction.
// Assumes that the output being spent has opTrueScript as it's scriptPublicKey
// Creates the value of the spent output minus 1 sompi
func CreateTransaction(txToSpend *externalapi.DomainTransaction) (*externalapi.DomainTransaction, error) {
func CreateTransaction(txToSpend *externalapi.DomainTransaction, fee uint64) (*externalapi.DomainTransaction, error) {
scriptPublicKey, redeemScript := OpTrueScript()
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
@@ -27,7 +27,7 @@ func CreateTransaction(txToSpend *externalapi.DomainTransaction) (*externalapi.D
}
output := &externalapi.DomainTransactionOutput{
ScriptPublicKey: scriptPublicKey,
Value: txToSpend.Outputs[0].Value - 1,
Value: txToSpend.Outputs[0].Value - fee,
}
return &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -14,7 +14,7 @@ import (
// public key and the public key. This function is used to generate randomized
// test data.
func genRandomSig() (*secp256k1.Hash, *secp256k1.SchnorrSignature, *secp256k1.SchnorrPublicKey, error) {
privKey, err := secp256k1.GeneratePrivateKey()
privKey, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
return nil, nil, nil, err
}

View File

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

View File

@@ -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,19 +180,19 @@ 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)
key, err := secp256k1.GeneratePrivateKey()
key, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Errorf("failed to make privKey for %s: %s",
msg, err)
@@ -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,12 +237,12 @@ 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)
key, err := secp256k1.GeneratePrivateKey()
key, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Errorf("failed to make privKey for %s: %s",
msg, err)
@@ -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,12 +314,12 @@ 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)
key, err := secp256k1.GeneratePrivateKey()
key, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Errorf("failed to make privKey for %s: %s",
msg, err)
@@ -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,12 +378,12 @@ 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)
key, err := secp256k1.GeneratePrivateKey()
key, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Errorf("failed to make privKey for %s: %s",
msg, err)
@@ -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,9 +470,9 @@ 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.GeneratePrivateKey()
key, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
return nil, nil, nil, errors.Errorf("failed to make privKey: %s", err)
}
@@ -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,
@@ -813,7 +806,7 @@ var sigScriptTests = []tstSigScript{
func TestSignatureScript(t *testing.T) {
t.Parallel()
privKey, _ := secp256k1.DeserializePrivateKey(&privKeyD)
privKey, _ := secp256k1.DeserializeSchnorrPrivateKey(&privKeyD)
nexttest:
for i := range sigScriptTests {
@@ -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",

View File

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

View File

@@ -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",

View File

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

View File

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

View File

@@ -36,8 +36,7 @@ func New(dagParams *dagconfig.Params, db infrastructuredatabase.Database, isArch
}
miningManagerFactory := miningmanager.NewFactory()
miningManager := miningManagerFactory.NewMiningManager(consensusInstance, dagParams.MaxMassAcceptedByBlock,
dagParams.RelayNonStdTxs)
miningManager := miningManagerFactory.NewMiningManager(consensusInstance, dagParams)
return &domain{
consensus: consensusInstance,

View File

@@ -2,21 +2,22 @@ package miningmanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/miningmanager/blocktemplatebuilder"
mempoolpkg "github.com/kaspanet/kaspad/domain/miningmanager/mempool"
)
// Factory instantiates new mining managers
type Factory interface {
NewMiningManager(consensus externalapi.Consensus, blockMaxMass uint64, acceptNonStd bool) MiningManager
NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params) MiningManager
}
type factory struct{}
// NewMiningManager instantiate a new mining manager
func (f *factory) NewMiningManager(consensus externalapi.Consensus, blockMaxMass uint64, acceptNonStd bool) MiningManager {
mempool := mempoolpkg.New(consensus, acceptNonStd)
blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool, blockMaxMass)
func (f *factory) NewMiningManager(consensus externalapi.Consensus, params *dagconfig.Params) MiningManager {
mempool := mempoolpkg.New(consensus, params)
blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool, params.MaxMassAcceptedByBlock)
return &miningManager{
mempool: mempool,

View File

@@ -7,6 +7,7 @@ package mempool
import (
"container/list"
"fmt"
"github.com/kaspanet/kaspad/domain/dagconfig"
"sort"
"sync"
"time"
@@ -88,16 +89,17 @@ type mempool struct {
// to on an unconditional timer.
nextExpireScan mstime.Time
mtx sync.RWMutex
policy policy
mtx sync.RWMutex
policy policy
dagParams *dagconfig.Params
}
// New returns a new memory pool for validating and storing standalone
// transactions until they are mined into a block.
func New(consensus consensusexternalapi.Consensus, acceptNonStd bool) miningmanagermodel.Mempool {
func New(consensus consensusexternalapi.Consensus, dagParams *dagconfig.Params) miningmanagermodel.Mempool {
policy := policy{
MaxTxVersion: constants.MaxTransactionVersion,
AcceptNonStd: acceptNonStd,
AcceptNonStd: dagParams.RelayNonStdTxs,
MaxOrphanTxs: 5,
MaxOrphanTxSize: 100000,
MinRelayTxFee: 1000, // 1 sompi per byte
@@ -113,6 +115,7 @@ func New(consensus consensusexternalapi.Consensus, acceptNonStd bool) miningmana
mempoolUTXOSet: newMempoolUTXOSet(),
consensus: consensus,
nextExpireScan: mstime.Now().Add(orphanExpireScanInterval),
dagParams: dagParams,
}
}
@@ -723,6 +726,11 @@ func (mp *mempool) maybeAcceptTransaction(tx *consensusexternalapi.DomainTransac
return nil, nil, err
}
if tx.Mass > mp.dagParams.MaxMassAcceptedByBlock {
return nil, nil, newRuleError(errors.Errorf("The transaction mass is %d which is "+
"higher than the maxmimum of %d", tx.Mass, mp.dagParams.MaxMassAcceptedByBlock))
}
// Don't allow transactions with non-standard inputs if the network
// parameters forbid their acceptance.
if !mp.policy.AcceptNonStd {

View File

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

View File

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

View File

@@ -0,0 +1,418 @@
package miningmanager_test
import (
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/kaspanet/kaspad/domain/miningmanager"
"github.com/pkg/errors"
"strings"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/dagconfig"
"testing"
)
const blockMaxMass uint64 = 10000000
// TestValidateAndInsertTransaction verifies that valid transactions were successfully inserted into the mempool.
func TestValidateAndInsertTransaction(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, "TestValidateAndInsertTransaction")
if err != nil {
t.Fatalf("Error setting up TestConsensus: %+v", err)
}
defer teardown(false)
miningFactory := miningmanager.NewFactory()
miningManager := miningFactory.NewMiningManager(tc, params)
transactionsToInsert := make([]*externalapi.DomainTransaction, 10)
for i := range transactionsToInsert {
transactionsToInsert[i] = createTransactionWithUTXOEntry(t, i)
err = miningManager.ValidateAndInsertTransaction(transactionsToInsert[i], true)
if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
}
// The UTXOEntry was filled manually for those transactions, so the transactions won't be considered orphans.
// Therefore, all the transactions expected to be contained in the mempool.
transactionsFromMempool := miningManager.AllTransactions()
if len(transactionsToInsert) != len(transactionsFromMempool) {
t.Fatalf("Wrong number of transactions in mempool: expected: %d, got: %d", len(transactionsToInsert), len(transactionsFromMempool))
}
for _, transactionToInsert := range transactionsToInsert {
if !contains(transactionToInsert, transactionsFromMempool) {
t.Fatalf("Missing transaction %s in the mempool", consensushashing.TransactionID(transactionToInsert))
}
}
// The parent's transaction was inserted by consensus(AddBlock), and we want to verify that
// the transaction is not considered an orphan and inserted into the mempool.
transactionNotAnOrphan, err := createChildTxWhenParentTxWasAddedByConsensus(params, tc)
if err != nil {
t.Fatalf("Error in createParentAndChildrenTransaction: %v", err)
}
err = miningManager.ValidateAndInsertTransaction(transactionNotAnOrphan, true)
if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
transactionsFromMempool = miningManager.AllTransactions()
if !contains(transactionNotAnOrphan, transactionsFromMempool) {
t.Fatalf("Missing transaction %s in the mempool", consensushashing.TransactionID(transactionNotAnOrphan))
}
})
}
// TestInsertDoubleTransactionsToMempool verifies that an attempt to insert a transaction
// more than once into the mempool will result in raising an appropriate error.
func TestInsertDoubleTransactionsToMempool(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestInsertDoubleTransactionsToMempool")
if err != nil {
t.Fatalf("Error setting up TestConsensus: %+v", err)
}
defer teardown(false)
miningFactory := miningmanager.NewFactory()
miningManager := miningFactory.NewMiningManager(tc, params)
transaction := createTransactionWithUTXOEntry(t, 0)
err = miningManager.ValidateAndInsertTransaction(transaction, true)
if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
err = miningManager.ValidateAndInsertTransaction(transaction, true)
if err == nil || !strings.Contains(err.Error(), "already have transaction") {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
})
}
// TestHandleNewBlockTransactions verifies that all the transactions in the block were successfully removed from the mempool.
func TestHandleNewBlockTransactions(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestHandleNewBlockTransactions")
if err != nil {
t.Fatalf("Error setting up TestConsensus: %+v", err)
}
defer teardown(false)
miningFactory := miningmanager.NewFactory()
miningManager := miningFactory.NewMiningManager(tc, params)
transactionsToInsert := make([]*externalapi.DomainTransaction, 10)
for i := range transactionsToInsert {
transaction := createTransactionWithUTXOEntry(t, i)
transactionsToInsert[i] = transaction
err = miningManager.ValidateAndInsertTransaction(transaction, true)
if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
}
const partialLength = 3
blockWithFirstPartOfTheTransactions := append([]*externalapi.DomainTransaction{nil}, transactionsToInsert[0:partialLength]...)
blockWithRestOfTheTransactions := append([]*externalapi.DomainTransaction{nil}, transactionsToInsert[partialLength:]...)
_, err = miningManager.HandleNewBlockTransactions(blockWithFirstPartOfTheTransactions)
if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err)
}
mempoolTransactions := miningManager.AllTransactions()
for _, removedTransaction := range blockWithFirstPartOfTheTransactions {
if contains(removedTransaction, mempoolTransactions) {
t.Fatalf("This transaction shouldnt be in mempool: %s", consensushashing.TransactionID(removedTransaction))
}
}
// There are no chained/double-spends transactions, and hence it is expected that all the other
// transactions, will still be included in the mempool.
mempoolTransactions = miningManager.AllTransactions()
for _, transaction := range blockWithRestOfTheTransactions[transactionhelper.CoinbaseTransactionIndex+1:] {
if !contains(transaction, mempoolTransactions) {
t.Fatalf("This transaction %s should be in mempool.", consensushashing.TransactionID(transaction))
}
}
// Handle all the other transactions.
_, err = miningManager.HandleNewBlockTransactions(blockWithRestOfTheTransactions)
if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err)
}
if len(miningManager.AllTransactions()) != 0 {
blockIDs := domainBlocksToBlockIds(miningManager.AllTransactions())
t.Fatalf("The mempool contains unexpected transactions: %s", blockIDs)
}
})
}
func domainBlocksToBlockIds(blocks []*externalapi.DomainTransaction) []*externalapi.DomainTransactionID {
blockIDs := make([]*externalapi.DomainTransactionID, len(blocks))
for i := range blockIDs {
blockIDs[i] = consensushashing.TransactionID(blocks[i])
}
return blockIDs
}
// TestDoubleSpends verifies that any transactions which are now double spends as a result of the block's new transactions
// will be removed from the mempool.
func TestDoubleSpends(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestDoubleSpends")
if err != nil {
t.Fatalf("Failed setting up TestConsensus: %+v", err)
}
defer teardown(false)
miningFactory := miningmanager.NewFactory()
miningManager := miningFactory.NewMiningManager(tc, params)
transactionInTheMempool := createTransactionWithUTXOEntry(t, 0)
err = miningManager.ValidateAndInsertTransaction(transactionInTheMempool, true)
if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
doubleSpendTransactionInTheBlock := createTransactionWithUTXOEntry(t, 0)
doubleSpendTransactionInTheBlock.Inputs[0].PreviousOutpoint = transactionInTheMempool.Inputs[0].PreviousOutpoint
blockTransactions := []*externalapi.DomainTransaction{nil, doubleSpendTransactionInTheBlock}
_, err = miningManager.HandleNewBlockTransactions(blockTransactions)
if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err)
}
if contains(transactionInTheMempool, miningManager.AllTransactions()) {
t.Fatalf("The transaction %s, shouldn't be in the mempool, since at least one "+
"output was already spent.", consensushashing.TransactionID(transactionInTheMempool))
}
})
}
// TestOrphanTransactions verifies that a transaction could be a part of a new block template, only if it's not an orphan.
func TestOrphanTransactions(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, "TestOrphanTransactions")
if err != nil {
t.Fatalf("Error setting up TestConsensus: %+v", err)
}
defer teardown(false)
miningFactory := miningmanager.NewFactory()
miningManager := miningFactory.NewMiningManager(tc, params)
// Before each parent transaction, We will add two blocks by consensus in order to fund the parent transactions.
parentTransactions, childTransactions, err := createArraysOfParentAndChildrenTransactions(tc)
if err != nil {
t.Fatalf("Error in createArraysOfParentAndChildrenTransactions: %v", err)
}
for _, orphanTransaction := range childTransactions {
err = miningManager.ValidateAndInsertTransaction(orphanTransaction, true)
if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err)
}
}
transactionsMempool := miningManager.AllTransactions()
for _, transaction := range transactionsMempool {
if contains(transaction, childTransactions) {
t.Fatalf("Error: an orphan transaction is exist in the mempool")
}
}
block, err := miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
ExtraData: nil})
if err != nil {
t.Fatalf("Failed get a block template: %v", err)
}
for _, transactionFromBlock := range block.Transactions[1:] {
for _, orphanTransaction := range childTransactions {
if consensushashing.TransactionID(transactionFromBlock) == consensushashing.TransactionID(orphanTransaction) {
t.Fatalf("Tranasaction with unknown parents is exist in a block that was built from GetTemplate option.")
}
}
}
tips, err := tc.Tips()
if err != nil {
t.Fatalf("Tips: %v.", err)
}
blockParentsTransactionsHash, _, err := tc.AddBlock(tips, nil, parentTransactions)
if err != nil {
t.Fatalf("AddBlock: %v", err)
}
_, _, err = tc.AddBlock([]*externalapi.DomainHash{blockParentsTransactionsHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %v", err)
}
blockParentsTransactions, err := tc.GetBlock(blockParentsTransactionsHash)
if err != nil {
t.Fatalf("GetBlock: %v", err)
}
_, err = miningManager.HandleNewBlockTransactions(blockParentsTransactions.Transactions)
if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err)
}
transactionsMempool = miningManager.AllTransactions()
if len(transactionsMempool) != len(childTransactions) {
t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool))
}
for _, transaction := range transactionsMempool {
if !contains(transaction, childTransactions) {
t.Fatalf("Error: the transaction %s, should be in the mempool since its not "+
"oprhan anymore.", consensushashing.TransactionID(transaction))
}
}
block, err = miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
ExtraData: nil})
if err != nil {
t.Fatalf("GetBlockTemplate: %v", err)
}
for _, transactionFromBlock := range block.Transactions[1:] {
isContained := false
for _, childTransaction := range childTransactions {
if *consensushashing.TransactionID(transactionFromBlock) == *consensushashing.TransactionID(childTransaction) {
isContained = true
break
}
}
if !isContained {
t.Fatalf("Error: Unknown Transaction %s in a block.", consensushashing.TransactionID(transactionFromBlock))
}
}
})
}
func createTransactionWithUTXOEntry(t *testing.T, i int) *externalapi.DomainTransaction {
prevOutTxID := externalapi.DomainTransactionID{}
prevOutPoint := externalapi.DomainOutpoint{TransactionID: prevOutTxID, Index: uint32(i)}
scriptPublicKey, redeemScript := testutils.OpTrueScript()
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
if err != nil {
t.Fatalf("PayToScriptHashSignatureScript: %v", err)
}
txInputWithMaxSequence := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint,
SignatureScript: signatureScript,
Sequence: constants.SequenceLockTimeIsSeconds,
UTXOEntry: utxo.NewUTXOEntry(
100000000, // 1 KAS
scriptPublicKey,
true,
uint64(5)),
}
txOut := externalapi.DomainTransactionOutput{
Value: 10000,
ScriptPublicKey: scriptPublicKey,
}
tx := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWithMaxSequence},
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
Fee: 289,
Mass: 1,
LockTime: 0}
return &tx
}
func createArraysOfParentAndChildrenTransactions(tc testapi.TestConsensus) ([]*externalapi.DomainTransaction,
[]*externalapi.DomainTransaction, error) {
const numOfTransactions = 5
transactions := make([]*externalapi.DomainTransaction, numOfTransactions)
parentTransactions := make([]*externalapi.DomainTransaction, len(transactions))
var err error
for i := range transactions {
parentTransactions[i], transactions[i], err = createParentAndChildrenTransactions(tc)
if err != nil {
return nil, nil, err
}
}
return parentTransactions, transactions, nil
}
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (*externalapi.DomainTransaction,
*externalapi.DomainTransaction, error) {
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
tips, err := tc.Tips()
if err != nil {
return nil, nil, err
}
_, _, err = tc.AddBlock(tips, nil, nil)
if err != nil {
return nil, nil, errors.Wrapf(err, "AddBlock: %v", err)
}
tips, err = tc.Tips()
if err != nil {
return nil, nil, err
}
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
if err != nil {
return nil, nil, errors.Wrap(err, "AddBlock: ")
}
fundingBlockForParent, err := tc.GetBlock(fundingBlockHashForParent)
if err != nil {
return nil, nil, errors.Wrap(err, "GetBlock: ")
}
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
txParent, err := testutils.CreateTransaction(fundingTransactionForParent, 1000)
if err != nil {
return nil, nil, err
}
txChild, err := testutils.CreateTransaction(txParent, 1000)
if err != nil {
return nil, nil, err
}
return txParent, txChild, nil
}
func createChildTxWhenParentTxWasAddedByConsensus(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
firstBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
if err != nil {
return nil, errors.Wrapf(err, "AddBlock: %v", err)
}
ParentBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{firstBlockHash}, nil, nil)
if err != nil {
return nil, errors.Wrap(err, "AddBlock: ")
}
ParentBlock, err := tc.GetBlock(ParentBlockHash)
if err != nil {
return nil, errors.Wrap(err, "GetBlock: ")
}
parentTransaction := ParentBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
txChild, err := testutils.CreateTransaction(parentTransaction, 1000)
if err != nil {
return nil, err
}
return txChild, nil
}
func contains(transaction *externalapi.DomainTransaction, transactions []*externalapi.DomainTransaction) bool {
for _, candidateTransaction := range transactions {
if candidateTransaction.Equal(transaction) {
return true
}
}
return false
}

Some files were not shown because too many files have changed in this diff Show More