mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-03 12:46:43 +00:00
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
This commit is contained in:
parent
6dd3d4a9e7
commit
a786cdc15e
@ -189,7 +189,7 @@ func (v *transactionValidator) validateTransactionScripts(tx *externalapi.Domain
|
||||
}
|
||||
|
||||
scriptPubKey := utxoEntry.ScriptPublicKey()
|
||||
vm, err := txscript.NewEngine(scriptPubKey, tx, i, txscript.ScriptNoFlags, v.sigCache, sighashReusedValues)
|
||||
vm, err := txscript.NewEngine(scriptPubKey, tx, i, txscript.ScriptNoFlags, v.sigCache, v.sigCacheECDSA, sighashReusedValues)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ruleerrors.ErrScriptMalformed, "failed to parse input "+
|
||||
"%d which references output %s - "+
|
||||
|
@ -21,6 +21,7 @@ type transactionValidator struct {
|
||||
massPerSigOp uint64
|
||||
maxCoinbasePayloadLength uint64
|
||||
sigCache *txscript.SigCache
|
||||
sigCacheECDSA *txscript.SigCacheECDSA
|
||||
}
|
||||
|
||||
// New instantiates a new TransactionValidator
|
||||
@ -47,5 +48,6 @@ func New(blockCoinbaseMaturity uint64,
|
||||
ghostdagDataStore: ghostdagDataStore,
|
||||
daaBlocksStore: daaBlocksStore,
|
||||
sigCache: txscript.NewSigCache(sigCacheSize),
|
||||
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
||||
}
|
||||
}
|
||||
|
@ -362,3 +362,127 @@ func TestSigningTwoInputs(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSigningTwoInputsECDSA(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestSigningTwoInputsECDSA")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
privateKey, err := secp256k1.GenerateECDSAPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a private key: %v", err)
|
||||
}
|
||||
publicKey, err := privateKey.ECDSAPublicKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a public key: %v", err)
|
||||
}
|
||||
publicKeySerialized, err := publicKey.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to serialize public key: %v", err)
|
||||
}
|
||||
addr, err := util.NewAddressPublicKeyECDSA(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pk address: %v", err)
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
|
||||
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block2Hash}, coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
block2, err := tc.GetBlock(block2Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block2: %+v", err)
|
||||
}
|
||||
|
||||
block3, err := tc.GetBlock(block3Hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting block3: %+v", err)
|
||||
}
|
||||
|
||||
block2Tx := block2.Transactions[0]
|
||||
block2TxOut := block2Tx.Outputs[0]
|
||||
|
||||
block3Tx := block3.Transactions[0]
|
||||
block3TxOut := block3Tx.Outputs[0]
|
||||
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block2.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block2TxOut.Value, block2TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(block3.Transactions[0]),
|
||||
Index: 0,
|
||||
},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(block3TxOut.Value, block3TxOut.ScriptPublicKey, true, 0),
|
||||
},
|
||||
},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{
|
||||
Value: 1,
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
Gas: 0,
|
||||
LockTime: 0,
|
||||
}
|
||||
|
||||
sighashReusedValues := &consensushashing.SighashReusedValues{}
|
||||
for i, input := range tx.Inputs {
|
||||
signatureScript, err := txscript.SignatureScriptECDSA(tx, i, consensushashing.SigHashAll, privateKey,
|
||||
sighashReusedValues)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a sigScript: %v", err)
|
||||
}
|
||||
input.SignatureScript = signatureScript
|
||||
}
|
||||
|
||||
_, insertionResult, err := tc.AddBlock([]*externalapi.DomainHash{block3Hash}, nil, []*externalapi.DomainTransaction{tx})
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
txOutpoint := &externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(tx),
|
||||
Index: 0,
|
||||
}
|
||||
if !insertionResult.VirtualUTXODiff.ToAdd().Contains(txOutpoint) {
|
||||
t.Fatalf("tx was not accepted by the DAG")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package consensushashing
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
@ -58,10 +57,10 @@ type SighashReusedValues struct {
|
||||
payloadHash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// CalculateSignatureHash will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification.
|
||||
// CalculateSignatureHashSchnorr will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification for Schnorr.
|
||||
// This returns error only if one of the provided parameters are consensus-invalid.
|
||||
func CalculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
func CalculateSignatureHashSchnorr(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
|
||||
|
||||
if !hashType.IsStandardSigHashType() {
|
||||
@ -70,18 +69,26 @@ func CalculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, h
|
||||
|
||||
txIn := tx.Inputs[inputIndex]
|
||||
prevScriptPublicKey := txIn.UTXOEntry.ScriptPublicKey()
|
||||
|
||||
if tx.Version > constants.MaxTransactionVersion {
|
||||
return nil, errors.Errorf("Transaction version is unknown.")
|
||||
}
|
||||
|
||||
if prevScriptPublicKey.Version > constants.MaxScriptPublicKeyVersion {
|
||||
return nil, errors.Errorf("Script version is unknown.")
|
||||
}
|
||||
|
||||
return calculateSignatureHash(tx, inputIndex, txIn, prevScriptPublicKey, hashType, reusedValues)
|
||||
}
|
||||
|
||||
// CalculateSignatureHashECDSA will, given a script and hash type calculate the signature hash
|
||||
// to be used for signing and verification for ECDSA.
|
||||
// This returns error only if one of the provided parameters are consensus-invalid.
|
||||
func CalculateSignatureHashECDSA(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
|
||||
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
|
||||
|
||||
hash, err := CalculateSignatureHashSchnorr(tx, inputIndex, hashType, reusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashWriter := hashes.NewTransactionSigningHashECDSAWriter()
|
||||
hashWriter.InfallibleWrite(hash.ByteSlice())
|
||||
|
||||
return hashWriter.Finalize(), nil
|
||||
}
|
||||
|
||||
func calculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, txIn *externalapi.DomainTransactionInput,
|
||||
prevScriptPublicKey *externalapi.ScriptPublicKey, hashType SigHashType, reusedValues *SighashReusedValues) (
|
||||
*externalapi.DomainHash, error) {
|
||||
|
@ -88,7 +88,7 @@ func modifySubnetworkID(tx *externalapi.DomainTransaction) *externalapi.DomainTr
|
||||
return clone
|
||||
}
|
||||
|
||||
func TestCalculateSignatureHash(t *testing.T) {
|
||||
func TestCalculateSignatureHashSchnorr(t *testing.T) {
|
||||
nativeTx, subnetworkTx, err := generateTxs()
|
||||
if err != nil {
|
||||
t.Fatalf("Error from generateTxs: %+v", err)
|
||||
@ -196,10 +196,132 @@ func TestCalculateSignatureHash(t *testing.T) {
|
||||
tx = test.modificationFunction(tx)
|
||||
}
|
||||
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHash(
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHashSchnorr(
|
||||
tx, test.inputIndex, test.hashType, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("%s: Error from CalculateSignatureHash: %+v", test.name, err)
|
||||
t.Errorf("%s: Error from CalculateSignatureHashSchnorr: %+v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if actualSignatureHash.String() != test.expectedSignatureHash {
|
||||
t.Errorf("%s: expected signature hash: '%s'; but got: '%s'",
|
||||
test.name, test.expectedSignatureHash, actualSignatureHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateSignatureHashECDSA(t *testing.T) {
|
||||
nativeTx, subnetworkTx, err := generateTxs()
|
||||
if err != nil {
|
||||
t.Fatalf("Error from generateTxs: %+v", err)
|
||||
}
|
||||
|
||||
// Note: Expected values were generated by the same code that they test,
|
||||
// As long as those were not verified using 3rd-party code they only check for regression, not correctness
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *externalapi.DomainTransaction
|
||||
hashType consensushashing.SigHashType
|
||||
inputIndex int
|
||||
modificationFunction func(*externalapi.DomainTransaction) *externalapi.DomainTransaction
|
||||
expectedSignatureHash string
|
||||
}{
|
||||
// native transactions
|
||||
|
||||
// sigHashAll
|
||||
{name: "native-all-0", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "150a2bcd0296f76a3395a4a9982df46bf24ce93f955bc39c10ffc95b6c524eb3"},
|
||||
{name: "native-all-0-modify-input-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // should change the hash
|
||||
expectedSignatureHash: "8fb5304e181b003e0c123ea6f6677abc3704feec47054e8a1c218b827bf57ca0"},
|
||||
{name: "native-all-0-modify-output-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // should change the hash
|
||||
expectedSignatureHash: "180cb36454aa80822694decde4fc711104e35a4bddf92286a83877f2b8d0aabb"},
|
||||
{name: "native-all-0-modify-sequence-1", tx: nativeTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // should change the hash
|
||||
expectedSignatureHash: "5b5f1c42a3c3c16bb4922777e2963c00e6a2cce39afa1980d2288053378f9632"},
|
||||
{name: "native-all-anyonecanpay-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-0", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(0), // should change the hash
|
||||
expectedSignatureHash: "1208491d564c138d613f51b997394dbad23feca7c0ca88c7f36cdf6b9173327d"},
|
||||
{name: "native-all-anyonecanpay-0-modify-input-1", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyInput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
{name: "native-all-anyonecanpay-0-modify-sequence", tx: nativeTx, hashType: allAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "9473ffbe0db4914f2cd8fe5d67479224a02eb031882d9170b785d0d2c7bfcd1b"},
|
||||
|
||||
// sigHashNone
|
||||
{name: "native-none-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-0-modify-output-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-0-modify-sequence-0", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "57d76e2568cd3fc3426b4f8836fe900a2d20e740fad744949126651fd549f75e"},
|
||||
{name: "native-none-0-modify-sequence-1", tx: nativeTx, hashType: none, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "6e427f26e4a9c1a7fc556a8aabdedb8799a897bc5d42a0a18615e5a0f7639d8f"},
|
||||
{name: "native-none-anyonecanpay-0", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "ef97a0f89d623302619f9aa2a00fce1522e72d4d255e6c6d3ed225ffc02f38ff"},
|
||||
{name: "native-none-anyonecanpay-0-modify-amount-spent", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyAmountSpent(0), // should change the hash
|
||||
expectedSignatureHash: "043a2a943f02607be126ac6609ab2324aae389d784a4147f27101e7da379311a"},
|
||||
{name: "native-none-anyonecanpay-0-modify-script-public-key", tx: nativeTx, hashType: noneAnyoneCanPay, inputIndex: 0,
|
||||
modificationFunction: modifyScriptPublicKey(0), // should change the hash
|
||||
expectedSignatureHash: "f2cd43d0d047cdcfbf8b6e12a86cfbf250f1e2c34dc5e631675a5f5b867bd9e6"},
|
||||
|
||||
// sigHashSingle
|
||||
{name: "native-single-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-0-modify-output-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(0), // should change the hash
|
||||
expectedSignatureHash: "c2c7e77516a15f0f47f886b14cc47af2045eea15f176a9a560a9d47d8866958f"},
|
||||
{name: "native-single-0-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-0-modify-sequence-0", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(0), // should change the hash
|
||||
expectedSignatureHash: "2034eec2acc08c49d3896cc1bda214904ca850fc5989518885465b5a3154ee7f"},
|
||||
{name: "native-single-0-modify-sequence-1", tx: nativeTx, hashType: single, inputIndex: 0,
|
||||
modificationFunction: modifySequence(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "1cf376b9f180f59a1b9a5e420390198c20e1ba79c39349271632145fda175247"},
|
||||
{name: "native-single-2-no-corresponding-output", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
expectedSignatureHash: "84ae3bb03202efc587d97e5aea7b80581b82242b969e6dea13b8daa32d24c0c1"},
|
||||
{name: "native-single-2-no-corresponding-output-modify-output-1", tx: nativeTx, hashType: single, inputIndex: 2,
|
||||
modificationFunction: modifyOutput(1), // shouldn't change the hash
|
||||
expectedSignatureHash: "84ae3bb03202efc587d97e5aea7b80581b82242b969e6dea13b8daa32d24c0c1"},
|
||||
{name: "native-single-anyonecanpay-0", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 0,
|
||||
expectedSignatureHash: "b2ccf259a65c3231d741a03420967b95563c3928cc15d3d15e8e795f383ab48b"},
|
||||
{name: "native-single-anyonecanpay-2-no-corresponding-output", tx: nativeTx, hashType: singleAnyoneCanPay, inputIndex: 2,
|
||||
expectedSignatureHash: "652c8cd0ac050e41aad347ea09ee788360eec70908ba22fe5bba5bdde49b8ae1"},
|
||||
|
||||
// subnetwork transaction
|
||||
{name: "subnetwork-all-0", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
expectedSignatureHash: "2e828c04f5f03e4ce4b3de1fa5303400da5fa504291b760f5f6d4e98fc24597f"},
|
||||
{name: "subnetwork-all-modify-payload", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyPayload, // should change the hash
|
||||
expectedSignatureHash: "d5f3993aa8b7f47df52f78f2be9965f928c9cca9ac9e9542f1190b9d5ed6c17d"},
|
||||
{name: "subnetwork-all-modify-gas", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifyGas, // should change the hash
|
||||
expectedSignatureHash: "e74d4a9fa5cdf476299ebdfa03f3c8021a157f814731ea11f6a6d606dc5cd439"},
|
||||
{name: "subnetwork-all-subnetwork-id", tx: subnetworkTx, hashType: all, inputIndex: 0,
|
||||
modificationFunction: modifySubnetworkID, // should change the hash
|
||||
expectedSignatureHash: "ca8bf9bc42cda2ec3ce8bee090011072e56ff4d0d8616d5c20cefe5f84d7fb37"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
tx := test.tx
|
||||
if test.modificationFunction != nil {
|
||||
tx = test.modificationFunction(tx)
|
||||
}
|
||||
|
||||
actualSignatureHash, err := consensushashing.CalculateSignatureHashECDSA(
|
||||
tx, test.inputIndex, test.hashType, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("%s: Error from CalculateSignatureHashECDSA: %+v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -283,7 +405,7 @@ func generateTxs() (nativeTx, subnetworkTx *externalapi.DomainTransaction, err e
|
||||
return nativeTx, subnetworkTx, nil
|
||||
}
|
||||
|
||||
func BenchmarkCalculateSignatureHash(b *testing.B) {
|
||||
func BenchmarkCalculateSignatureHashSchnorr(b *testing.B) {
|
||||
sigHashTypes := []consensushashing.SigHashType{
|
||||
consensushashing.SigHashAll,
|
||||
consensushashing.SigHashNone,
|
||||
@ -300,9 +422,9 @@ func BenchmarkCalculateSignatureHash(b *testing.B) {
|
||||
reusedValues := &consensushashing.SighashReusedValues{}
|
||||
for inputIndex := range tx.Inputs {
|
||||
sigHashType := sigHashTypes[inputIndex%len(sigHashTypes)]
|
||||
_, err := consensushashing.CalculateSignatureHash(tx, inputIndex, sigHashType, reusedValues)
|
||||
_, err := consensushashing.CalculateSignatureHashSchnorr(tx, inputIndex, sigHashType, reusedValues)
|
||||
if err != nil {
|
||||
b.Fatalf("Error from CalculateSignatureHash: %+v", err)
|
||||
b.Fatalf("Error from CalculateSignatureHashSchnorr: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,28 +22,6 @@ const (
|
||||
txEncodingExcludeSignatureScript = 1 << iota
|
||||
)
|
||||
|
||||
// TransactionHashForSigning hashes the transaction and the given hash type in a way that is intended for
|
||||
// signatures.
|
||||
func TransactionHashForSigning(tx *externalapi.DomainTransaction, hashType uint32) *externalapi.DomainHash {
|
||||
// Encode the header and hash everything prior to the number of
|
||||
// transactions.
|
||||
writer := hashes.NewTransactionSigningHashWriter()
|
||||
err := serializeTransaction(writer, tx, txEncodingFull)
|
||||
if err != nil {
|
||||
// It seems like this could only happen if the writer returned an error.
|
||||
// and this writer should never return an error (no allocations or possible failures)
|
||||
// the only non-writer error path here is unknown types in `WriteElement`
|
||||
panic(errors.Wrap(err, "TransactionHashForSigning() failed. this should never fail for structurally-valid transactions"))
|
||||
}
|
||||
|
||||
err = serialization.WriteElement(writer, hashType)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "this should never happen. Hash digest should never return an error"))
|
||||
}
|
||||
|
||||
return writer.Finalize()
|
||||
}
|
||||
|
||||
// TransactionHash returns the transaction hash.
|
||||
func TransactionHash(tx *externalapi.DomainTransaction) *externalapi.DomainHash {
|
||||
// Encode the header and hash everything prior to the number of
|
||||
|
@ -1,19 +1,26 @@
|
||||
package hashes
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
transcationHashDomain = "TransactionHash"
|
||||
transcationIDDomain = "TransactionID"
|
||||
transcationSigningDomain = "TransactionSigningHash"
|
||||
blockDomain = "BlockHash"
|
||||
proofOfWorkDomain = "ProofOfWorkHash"
|
||||
merkleBranchDomain = "MerkleBranchHash"
|
||||
transcationHashDomain = "TransactionHash"
|
||||
transcationIDDomain = "TransactionID"
|
||||
transcationSigningDomain = "TransactionSigningHash"
|
||||
transcationSigningECDSADomain = "TransactionSigningHashECDSA"
|
||||
blockDomain = "BlockHash"
|
||||
proofOfWorkDomain = "ProofOfWorkHash"
|
||||
merkleBranchDomain = "MerkleBranchHash"
|
||||
)
|
||||
|
||||
// transactionSigningECDSADomainHash is a hashed version of transcationSigningECDSADomain that is used
|
||||
// to make it a constant size. This is needed because this domain is used by sha256 hash writer, and
|
||||
// sha256 doesn't support variable size domain separation.
|
||||
var transactionSigningECDSADomainHash = sha256.Sum256([]byte(transcationSigningECDSADomain))
|
||||
|
||||
// NewTransactionHashWriter Returns a new HashWriter used for transaction hashes
|
||||
func NewTransactionHashWriter() HashWriter {
|
||||
blake, err := blake2b.New256([]byte(transcationHashDomain))
|
||||
@ -41,6 +48,13 @@ func NewTransactionSigningHashWriter() HashWriter {
|
||||
return HashWriter{blake}
|
||||
}
|
||||
|
||||
// NewTransactionSigningHashECDSAWriter Returns a new HashWriter used for signing on a transaction with ECDSA
|
||||
func NewTransactionSigningHashECDSAWriter() HashWriter {
|
||||
hashWriter := HashWriter{sha256.New()}
|
||||
hashWriter.InfallibleWrite(transactionSigningECDSADomainHash[:])
|
||||
return hashWriter
|
||||
}
|
||||
|
||||
// NewBlockHashWriter Returns a new HashWriter used for hashing blocks
|
||||
func NewBlockHashWriter() HashWriter {
|
||||
blake, err := blake2b.New256([]byte(blockDomain))
|
||||
|
@ -53,6 +53,7 @@ type Engine struct {
|
||||
numOps int
|
||||
flags ScriptFlags
|
||||
sigCache *SigCache
|
||||
sigCacheECDSA *SigCacheECDSA
|
||||
sigHashReusedValues *consensushashing.SighashReusedValues
|
||||
isP2SH bool // treat execution as pay-to-script-hash
|
||||
savedFirstStack [][]byte // stack from first script for ps2h scripts
|
||||
@ -369,6 +370,14 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
||||
return scriptError(ErrPubKeyFormat, "unsupported public key type")
|
||||
}
|
||||
|
||||
func (vm *Engine) checkPubKeyEncodingECDSA(pubKey []byte) error {
|
||||
if len(pubKey) == 33 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scriptError(ErrPubKeyFormat, "unsupported public key type")
|
||||
}
|
||||
|
||||
// checkSignatureLength returns whether or not the passed signature is
|
||||
// in the correct Schnorr format.
|
||||
func (vm *Engine) checkSignatureLength(sig []byte) error {
|
||||
@ -379,6 +388,14 @@ func (vm *Engine) checkSignatureLength(sig []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *Engine) checkSignatureLengthECDSA(sig []byte) error {
|
||||
if len(sig) != 64 {
|
||||
message := fmt.Sprintf("invalid signature length %d", len(sig))
|
||||
return scriptError(ErrSigLength, message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStack returns the contents of stack as a byte array bottom up
|
||||
func getStack(stack *stack) [][]byte {
|
||||
array := make([][]byte, stack.Depth())
|
||||
@ -428,7 +445,7 @@ func (vm *Engine) SetAltStack(data [][]byte) {
|
||||
// transaction, and input index. The flags modify the behavior of the script
|
||||
// engine according to the description provided by each flag.
|
||||
func NewEngine(scriptPubKey *externalapi.ScriptPublicKey, tx *externalapi.DomainTransaction, txIdx int, flags ScriptFlags,
|
||||
sigCache *SigCache, sighashReusedValues *consensushashing.SighashReusedValues) (*Engine, error) {
|
||||
sigCache *SigCache, sigCacheECDSA *SigCacheECDSA, sighashReusedValues *consensushashing.SighashReusedValues) (*Engine, error) {
|
||||
|
||||
// The provided transaction input index must refer to a valid input.
|
||||
if txIdx < 0 || txIdx >= len(tx.Inputs) {
|
||||
@ -446,7 +463,7 @@ func NewEngine(scriptPubKey *externalapi.ScriptPublicKey, tx *externalapi.Domain
|
||||
return nil, scriptError(ErrEvalFalse,
|
||||
"false stack entry at end of script execution")
|
||||
}
|
||||
vm := Engine{scriptVersion: scriptPubKey.Version, flags: flags, sigCache: sigCache}
|
||||
vm := Engine{scriptVersion: scriptPubKey.Version, flags: flags, sigCache: sigCache, sigCacheECDSA: sigCacheECDSA}
|
||||
|
||||
if vm.scriptVersion > constants.MaxScriptPublicKeyVersion {
|
||||
return &vm, nil
|
||||
|
@ -56,7 +56,7 @@ func TestBadPC(t *testing.T) {
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("NOP", 0), Version: 0}
|
||||
|
||||
for _, test := range tests {
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create script: %v", err)
|
||||
}
|
||||
@ -124,7 +124,7 @@ func TestCheckErrorCondition(t *testing.T) {
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm(test.script, 0), Version: 0}
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Errorf("TestCheckErrorCondition: %d: failed to create script: %v", i, err)
|
||||
}
|
||||
@ -252,7 +252,7 @@ func TestDisasmPC(t *testing.T) {
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("OP_DROP NOP TRUE", 0), Version: 0}
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create script: %v", err)
|
||||
}
|
||||
@ -315,7 +315,7 @@ func TestDisasmScript(t *testing.T) {
|
||||
}
|
||||
|
||||
scriptPubKey := &externalapi.ScriptPublicKey{Script: mustParseShortForm("OP_DROP NOP TRUE", 0), Version: 0}
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil, nil, &consensushashing.SighashReusedValues{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create script: %v", err)
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ const (
|
||||
OpSHA256 = 0xa8 // 168
|
||||
OpUnknown169 = 0xa9 // 169
|
||||
OpBlake2b = 0xaa // 170
|
||||
OpUnknown171 = 0xab // 171
|
||||
OpCheckSigECDSA = 0xab // 171
|
||||
OpCheckSig = 0xac // 172
|
||||
OpCheckSigVerify = 0xad // 173
|
||||
OpCheckMultiSig = 0xae // 174
|
||||
@ -487,6 +487,7 @@ var opcodeArray = [256]opcode{
|
||||
// Crypto opcodes.
|
||||
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},
|
||||
@ -508,7 +509,6 @@ var opcodeArray = [256]opcode{
|
||||
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.
|
||||
@ -2194,7 +2277,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
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||
0xa3: "OP_MIN", 0xa4: "OP_MAX", 0xa5: "OP_WITHIN",
|
||||
0xa8: "OP_SHA256",
|
||||
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,7 +186,7 @@ func TestOpcodeDisasm(t *testing.T) {
|
||||
}
|
||||
|
||||
func isOpUnknown(opcodeVal int) bool {
|
||||
return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc || opcodeVal == 0xab ||
|
||||
return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc ||
|
||||
opcodeVal == 0xa6 || opcodeVal == 0xa7 || opcodeVal == 0xa9
|
||||
}
|
||||
|
||||
|
@ -260,8 +260,10 @@ func createSpendingTx(sigScript []byte, scriptPubKey *externalapi.ScriptPublicKe
|
||||
func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// Create a signature cache to use only if requested.
|
||||
var sigCache *SigCache
|
||||
var sigCacheECDSA *SigCacheECDSA
|
||||
if useSigCache {
|
||||
sigCache = NewSigCache(10)
|
||||
sigCacheECDSA = NewSigCacheECDSA(10)
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
@ -343,7 +345,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
|
||||
// used, then create a new engine to execute the scripts.
|
||||
tx := createSpendingTx(scriptSig, scriptPubKey)
|
||||
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, &consensushashing.SighashReusedValues{})
|
||||
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, sigCacheECDSA, &consensushashing.SighashReusedValues{})
|
||||
if err == nil {
|
||||
err = vm.Execute()
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ type sigCacheEntry struct {
|
||||
pubKey *secp256k1.SchnorrPublicKey
|
||||
}
|
||||
|
||||
// SigCache implements an ECDSA signature verification cache with a randomized
|
||||
// SigCache implements an Schnorr signature verification cache with a randomized
|
||||
// entry eviction policy. Only valid signatures will be added to the cache. The
|
||||
// benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS
|
||||
// attack wherein an attack causes a victim's client to hang due to worst-case
|
||||
|
90
domain/consensus/utils/txscript/sigcache_ecdsa.go
Normal file
90
domain/consensus/utils/txscript/sigcache_ecdsa.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
)
|
||||
|
||||
// sigCacheEntryECDSA represents an entry in the SigCache. Entries within the
|
||||
// SigCache are keyed according to the sigHash of the signature. In the
|
||||
// scenario of a cache-hit (according to the sigHash), an additional comparison
|
||||
// of the signature, and public key will be executed in order to ensure a complete
|
||||
// match. In the occasion that two sigHashes collide, the newer sigHash will
|
||||
// simply overwrite the existing entry.
|
||||
type sigCacheEntryECDSA struct {
|
||||
sig *secp256k1.ECDSASignature
|
||||
pubKey *secp256k1.ECDSAPublicKey
|
||||
}
|
||||
|
||||
// SigCacheECDSA implements an ECDSA signature verification cache with a randomized
|
||||
// entry eviction policy. Only valid signatures will be added to the cache. The
|
||||
// benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS
|
||||
// attack wherein an attack causes a victim's client to hang due to worst-case
|
||||
// behavior triggered while processing attacker crafted invalid transactions. A
|
||||
// detailed description of the mitigated DoS attack can be found here:
|
||||
// https://bitslog.wordpress.com/2013/01/23/fixed-bitcoin-vulnerability-explanation-why-the-signature-cache-is-a-dos-protection/.
|
||||
// Secondly, usage of the SigCache introduces a signature verification
|
||||
// optimization which speeds up the validation of transactions within a block,
|
||||
// if they've already been seen and verified within the mempool.
|
||||
type SigCacheECDSA struct {
|
||||
validSigs map[secp256k1.Hash]sigCacheEntryECDSA
|
||||
maxEntries uint
|
||||
}
|
||||
|
||||
// NewSigCacheECDSA creates and initializes a new instance of SigCache. Its sole
|
||||
// parameter 'maxEntries' represents the maximum number of entries allowed to
|
||||
// exist in the SigCache at any particular moment. Random entries are evicted
|
||||
// to make room for new entries that would cause the number of entries in the
|
||||
// cache to exceed the max.
|
||||
func NewSigCacheECDSA(maxEntries uint) *SigCacheECDSA {
|
||||
return &SigCacheECDSA{
|
||||
validSigs: make(map[secp256k1.Hash]sigCacheEntryECDSA, maxEntries),
|
||||
maxEntries: maxEntries,
|
||||
}
|
||||
}
|
||||
|
||||
// Exists returns true if an existing entry of 'sig' over 'sigHash' for public
|
||||
// key 'pubKey' is found within the SigCache. Otherwise, false is returned.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Readers won't be blocked
|
||||
// unless there exists a writer, adding an entry to the SigCache.
|
||||
func (s *SigCacheECDSA) Exists(sigHash secp256k1.Hash, sig *secp256k1.ECDSASignature, pubKey *secp256k1.ECDSAPublicKey) bool {
|
||||
entry, ok := s.validSigs[sigHash]
|
||||
|
||||
return ok && entry.pubKey.IsEqual(pubKey) && entry.sig.IsEqual(sig)
|
||||
}
|
||||
|
||||
// Add adds an entry for a signature over 'sigHash' under public key 'pubKey'
|
||||
// to the signature cache. In the event that the SigCache is 'full', an
|
||||
// existing entry is randomly chosen to be evicted in order to make space for
|
||||
// the new entry.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Writers will block
|
||||
// simultaneous readers until function execution has concluded.
|
||||
func (s *SigCacheECDSA) Add(sigHash secp256k1.Hash, sig *secp256k1.ECDSASignature, pubKey *secp256k1.ECDSAPublicKey) {
|
||||
if s.maxEntries == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If adding this new entry will put us over the max number of allowed
|
||||
// entries, then evict an entry.
|
||||
if uint(len(s.validSigs)+1) > s.maxEntries {
|
||||
// Remove a random entry from the map. Relying on the random
|
||||
// starting point of Go's map iteration. It's worth noting that
|
||||
// the random iteration starting point is not 100% guaranteed
|
||||
// by the spec, however most Go compilers support it.
|
||||
// Ultimately, the iteration order isn't important here because
|
||||
// in order to manipulate which items are evicted, an adversary
|
||||
// would need to be able to execute preimage attacks on the
|
||||
// hashing function in order to start eviction at a specific
|
||||
// entry.
|
||||
for sigEntry := range s.validSigs {
|
||||
delete(s.validSigs, sigEntry)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.validSigs[sigHash] = sigCacheEntryECDSA{sig, pubKey}
|
||||
}
|
@ -19,7 +19,7 @@ import (
|
||||
func RawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
key *secp256k1.SchnorrKeyPair, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
hash, err := consensushashing.CalculateSignatureHash(tx, idx, hashType, sighashReusedValues)
|
||||
hash, err := consensushashing.CalculateSignatureHashSchnorr(tx, idx, hashType, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -32,8 +32,26 @@ func RawTxInSignature(tx *externalapi.DomainTransaction, idx int, hashType conse
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// RawTxInSignatureECDSA returns the serialized ECDSA signature for the input idx of
|
||||
// the given transaction, with hashType appended to it.
|
||||
func RawTxInSignatureECDSA(tx *externalapi.DomainTransaction, idx int, hashType consensushashing.SigHashType,
|
||||
key *secp256k1.ECDSAPrivateKey, sighashReusedValues *consensushashing.SighashReusedValues) ([]byte, error) {
|
||||
|
||||
hash, err := consensushashing.CalculateSignatureHashECDSA(tx, idx, hashType, sighashReusedValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secpHash := secp256k1.Hash(*hash.ByteArray())
|
||||
signature, err := key.ECDSASign(&secpHash)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot sign tx input: %s", err)
|
||||
}
|
||||
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// SignatureScript creates an input signature script for tx to spend KAS sent
|
||||
// from a previous output to the owner of privKey. tx must include all
|
||||
// from a previous output to the owner of a Schnorr private key. tx must include all
|
||||
// transaction inputs and outputs, however txin scripts are allowed to be filled
|
||||
// or empty. The returned script is calculated to be used as the idx'th txin
|
||||
// sigscript for tx. script is the ScriptPublicKey of the previous output being used
|
||||
@ -51,6 +69,25 @@ func SignatureScript(tx *externalapi.DomainTransaction, idx int, hashType consen
|
||||
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).Script()
|
||||
}
|
||||
|
||||
func sign(dagParams *dagconfig.Params, tx *externalapi.DomainTransaction, idx int,
|
||||
script *externalapi.ScriptPublicKey, hashType consensushashing.SigHashType,
|
||||
sighashReusedValues *consensushashing.SighashReusedValues, kdb KeyDB, sdb ScriptDB) (
|
||||
|
@ -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)
|
||||
@ -870,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",
|
||||
|
@ -25,6 +25,13 @@ const (
|
||||
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{
|
||||
@ -167,8 +174,7 @@ func CalcScriptInfo(sigScript, scriptPubKey []byte, isP2SH bool) (*ScriptInfo, e
|
||||
}
|
||||
|
||||
// payToPubKeyScript creates a new script to pay a transaction
|
||||
// output to a 32-byte pubkey. It is expected that the input is a valid
|
||||
// hash.
|
||||
// output to a 32-byte pubkey.
|
||||
func payToPubKeyScript(pubKey []byte) ([]byte, error) {
|
||||
return NewScriptBuilder().
|
||||
AddData(pubKey).
|
||||
@ -176,6 +182,15 @@ func payToPubKeyScript(pubKey []byte) ([]byte, error) {
|
||||
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()
|
||||
}
|
||||
|
||||
// payToScriptHashScript creates a new script to pay a transaction output to a
|
||||
// script hash. It is expected that the input is a valid hash.
|
||||
func payToScriptHashScript(scriptHash []byte) ([]byte, error) {
|
||||
@ -197,7 +212,20 @@ 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, 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 {
|
||||
@ -208,7 +236,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 "+
|
||||
|
@ -23,6 +23,9 @@ const (
|
||||
// PubKey addresses always have the version byte set to 0.
|
||||
pubKeyAddrID = 0x00
|
||||
|
||||
// PubKey addresses always have the version byte set to 1.
|
||||
pubKeyECDSAAddrID = 0x01
|
||||
|
||||
// ScriptHash addresses always have the version byte set to 8.
|
||||
scriptHashAddrID = 0x08
|
||||
)
|
||||
@ -142,6 +145,8 @@ func DecodeAddress(addr string, expectedPrefix Bech32Prefix) (Address, error) {
|
||||
switch version {
|
||||
case pubKeyAddrID:
|
||||
return newAddressPubKey(prefix, decoded)
|
||||
case pubKeyECDSAAddrID:
|
||||
return newAddressPubKeyECDSA(prefix, decoded)
|
||||
case scriptHashAddrID:
|
||||
return newAddressScriptHashFromHash(prefix, decoded)
|
||||
default:
|
||||
@ -211,6 +216,68 @@ func (a *AddressPublicKey) String() string {
|
||||
return a.EncodeAddress()
|
||||
}
|
||||
|
||||
// PublicKeySizeECDSA is the public key size for an ECDSA public key
|
||||
const PublicKeySizeECDSA = 33
|
||||
|
||||
// AddressPublicKeyECDSA is an Address for a pay-to-pubkey (P2PK)
|
||||
// ECDSA transaction.
|
||||
type AddressPublicKeyECDSA struct {
|
||||
prefix Bech32Prefix
|
||||
publicKey [PublicKeySizeECDSA]byte
|
||||
}
|
||||
|
||||
// NewAddressPublicKeyECDSA returns a new AddressPublicKeyECDSA. publicKey must be 33
|
||||
// bytes.
|
||||
func NewAddressPublicKeyECDSA(publicKey []byte, prefix Bech32Prefix) (*AddressPublicKeyECDSA, error) {
|
||||
return newAddressPubKeyECDSA(prefix, publicKey)
|
||||
}
|
||||
|
||||
// newAddressPubKeyECDSA is the internal API to create an ECDSA pubkey address
|
||||
// with a known leading identifier byte for a network, rather than looking
|
||||
// it up through its parameters. This is useful when creating a new address
|
||||
// structure from a string encoding where the identifier byte is already
|
||||
// known.
|
||||
func newAddressPubKeyECDSA(prefix Bech32Prefix, publicKey []byte) (*AddressPublicKeyECDSA, error) {
|
||||
// Check for a valid pubkey length.
|
||||
if len(publicKey) != PublicKeySizeECDSA {
|
||||
return nil, errors.Errorf("publicKey must be %d bytes", PublicKeySizeECDSA)
|
||||
}
|
||||
|
||||
addr := &AddressPublicKeyECDSA{prefix: prefix}
|
||||
copy(addr.publicKey[:], publicKey)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// EncodeAddress returns the string encoding of a pay-to-pubkey
|
||||
// address. Part of the Address interface.
|
||||
func (a *AddressPublicKeyECDSA) EncodeAddress() string {
|
||||
return encodeAddress(a.prefix, a.publicKey[:], pubKeyECDSAAddrID)
|
||||
}
|
||||
|
||||
// ScriptAddress returns the bytes to be included in a txout script to pay
|
||||
// to a pubkey. Part of the Address interface.
|
||||
func (a *AddressPublicKeyECDSA) ScriptAddress() []byte {
|
||||
return a.publicKey[:]
|
||||
}
|
||||
|
||||
// IsForPrefix returns whether or not the pay-to-pubkey address is associated
|
||||
// with the passed kaspa network.
|
||||
func (a *AddressPublicKeyECDSA) IsForPrefix(prefix Bech32Prefix) bool {
|
||||
return a.prefix == prefix
|
||||
}
|
||||
|
||||
// Prefix returns the prefix for this address
|
||||
func (a *AddressPublicKeyECDSA) Prefix() Bech32Prefix {
|
||||
return a.prefix
|
||||
}
|
||||
|
||||
// String returns a human-readable string for the pay-to-pubkey address.
|
||||
// This is equivalent to calling EncodeAddress, but is provided so the type can
|
||||
// be used as a fmt.Stringer.
|
||||
func (a *AddressPublicKeyECDSA) String() string {
|
||||
return a.EncodeAddress()
|
||||
}
|
||||
|
||||
// AddressScriptHash is an Address for a pay-to-script-publicKey (P2SH)
|
||||
// transaction.
|
||||
type AddressScriptHash struct {
|
||||
|
@ -102,6 +102,32 @@ func TestAddresses(t *testing.T) {
|
||||
expectedPrefix: util.Bech32PrefixKaspaTest,
|
||||
},
|
||||
|
||||
// ECDSA P2PK tests.
|
||||
{
|
||||
name: "mainnet ecdsa p2pk",
|
||||
addr: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
||||
encoded: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
||||
valid: true,
|
||||
result: util.TstAddressPubKeyECDSA(
|
||||
util.Bech32PrefixKaspa,
|
||||
[util.PublicKeySizeECDSA]byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c, 0xaa,
|
||||
}),
|
||||
f: func() (util.Address, error) {
|
||||
publicKey := []byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c, 0xaa}
|
||||
return util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixUnknown,
|
||||
expectedPrefix: util.Bech32PrefixKaspa,
|
||||
},
|
||||
|
||||
// Negative P2PK tests.
|
||||
{
|
||||
name: "p2pk wrong public key length",
|
||||
@ -270,6 +296,9 @@ func TestAddresses(t *testing.T) {
|
||||
case *util.AddressPublicKey:
|
||||
saddr = util.TstAddressSAddrP2PK(encoded)
|
||||
|
||||
case *util.AddressPublicKeyECDSA:
|
||||
saddr = util.TstAddressSAddrP2PKECDSA(encoded)
|
||||
|
||||
case *util.AddressScriptHash:
|
||||
saddr = util.TstAddressSAddrP2SH(encoded)
|
||||
}
|
||||
@ -337,6 +366,12 @@ func TestAddresses(t *testing.T) {
|
||||
test.name)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(addr, decoded) {
|
||||
t.Errorf("%v: created address does not match the decoded address",
|
||||
test.name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,13 @@ func TstAddressPubKey(prefix Bech32Prefix, hash [PublicKeySize]byte) *AddressPub
|
||||
}
|
||||
}
|
||||
|
||||
func TstAddressPubKeyECDSA(prefix Bech32Prefix, hash [PublicKeySizeECDSA]byte) *AddressPublicKeyECDSA {
|
||||
return &AddressPublicKeyECDSA{
|
||||
prefix: prefix,
|
||||
publicKey: hash,
|
||||
}
|
||||
}
|
||||
|
||||
// TstAddressScriptHash makes an AddressScriptHash, setting the
|
||||
// unexported fields with the parameters hash and netID.
|
||||
func TstAddressScriptHash(prefix Bech32Prefix, hash [blake2b.Size256]byte) *AddressScriptHash {
|
||||
@ -46,6 +53,13 @@ func TstAddressSAddrP2PK(addr string) []byte {
|
||||
return decoded[:PublicKeySize]
|
||||
}
|
||||
|
||||
// TstAddressSAddr returns the expected script address bytes for
|
||||
// ECDSA P2PK kaspa addresses.
|
||||
func TstAddressSAddrP2PKECDSA(addr string) []byte {
|
||||
_, decoded, _, _ := bech32.Decode(addr)
|
||||
return decoded[:PublicKeySizeECDSA]
|
||||
}
|
||||
|
||||
// TstAddressSAddrP2SH returns the expected script address bytes for
|
||||
// P2SH kaspa addresses.
|
||||
func TstAddressSAddrP2SH(addr string) []byte {
|
||||
|
Loading…
x
Reference in New Issue
Block a user