dcrd/txscript/sigcache.go
Dave Collins 76a18d1716
secp256k1/ecdsa: Decouple ECDSA from secp256k1.
Currently, the secp256k1 package is rather tightly bound to ECDSA
signatures which makes them the only real first-class citizen in the
code.  In an effort to make it clear the ECDSA is only one possible
digital signature algorithms and to enable Schnorr signatures to also be
made first class citizens, this decouples all code related to producing
and parsing ECDSA signatures into a separate package under secp256k1
named ecdsa.

This is a fairly large change in terms of the number of lines changed,
however, the vast majority of the changes are mechanical to deal with
the package name changes and moved code.  The only real new code is
documentation for the new package.

The following is a high level overview of the changes:

- Rename signature.go to ecdsa/signature.go
- Rename signature_test.go to ecdsa/signature_test.go
- Rename sigerror.go to ecdsa/error.go
  - Rename SignatureErrorCode type to ErrorCode
  - Rename SignatureError type to Error
- Rename sigerror_test.go to ecdsa/error_test.go
- Rename signature_bench_test.go to ecdsa/bench_test.go
- Move signing-related examples from example_test.go to
  ecdsa/example_test.go
- Update all moved code to reference secp256k1 types and funcs
- Remove Sign from the secp256k1.PrivateKey type in favor of ecdsa.Sign
- Add ecdsa/README.md to describe the new package
- Add ecdsa/doc.go to provide package documentation via godoc
- Move signing examples from README.md to new ecdsa/README.md
- Update txscript and rpcserver to use the new package methods
2020-03-28 13:32:04 -05:00

106 lines
4.2 KiB
Go

// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
import (
"bytes"
"sync"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v3"
"github.com/decred/dcrd/dcrec/secp256k1/v3/ecdsa"
)
// sigCacheEntry 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 sigCacheEntry struct {
sig *ecdsa.Signature
pubKey *secp256k1.PublicKey
}
// SigCache 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 SigCache struct {
sync.RWMutex
validSigs map[chainhash.Hash]sigCacheEntry
maxEntries uint
}
// NewSigCache 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 NewSigCache(maxEntries uint) *SigCache {
return &SigCache{
validSigs: make(map[chainhash.Hash]sigCacheEntry, 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 *SigCache) Exists(sigHash chainhash.Hash, sig *ecdsa.Signature, pubKey *secp256k1.PublicKey) bool {
s.RLock()
entry, ok := s.validSigs[sigHash]
s.RUnlock()
return ok &&
bytes.Equal(entry.pubKey.SerializeCompressed(),
pubKey.SerializeCompressed()) &&
bytes.Equal(entry.sig.Serialize(), sig.Serialize())
}
// 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 *SigCache) Add(sigHash chainhash.Hash, sig *ecdsa.Signature, pubKey *secp256k1.PublicKey) {
s.Lock()
defer s.Unlock()
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] = sigCacheEntry{sig, pubKey}
}