2015-09-25 01:22:00 +02:00
|
|
|
// Copyright (c) 2015 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 (
|
|
|
|
"bytes"
|
|
|
|
"crypto/rand"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
)
|
|
|
|
|
|
|
|
// sigInfo represents an entry in the SigCache. Entries in the sigcache are a
|
|
|
|
// 3-tuple: (sigHash, sig, pubKey).
|
|
|
|
type sigInfo struct {
|
|
|
|
sigHash wire.ShaHash
|
|
|
|
sig string
|
|
|
|
pubKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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[sigInfo]struct{}
|
|
|
|
maxEntries uint
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSigCache creates and initializes a new instance of SigCache. Its sole
|
|
|
|
// parameter 'maxEntries' represents the maximum number of entries allowed to
|
2015-12-30 19:53:44 +01:00
|
|
|
// exist in the SigCache at any particular moment. Random entries are evicted
|
2015-09-25 01:22:00 +02:00
|
|
|
// 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[sigInfo]struct{}), 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 wire.ShaHash, sig *btcec.Signature, pubKey *btcec.PublicKey) bool {
|
|
|
|
info := sigInfo{sigHash, string(sig.Serialize()),
|
|
|
|
string(pubKey.SerializeCompressed())}
|
|
|
|
|
|
|
|
s.RLock()
|
|
|
|
_, ok := s.validSigs[info]
|
|
|
|
s.RUnlock()
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2015-12-30 19:53:44 +01:00
|
|
|
// existing entry is randomly chosen to be evicted in order to make space for
|
2015-09-25 01:22:00 +02:00
|
|
|
// 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 wire.ShaHash, sig *btcec.Signature, pubKey *btcec.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 {
|
|
|
|
// Generate a cryptographically random hash.
|
|
|
|
randHashBytes := make([]byte, wire.HashSize)
|
|
|
|
_, err := rand.Read(randHashBytes)
|
|
|
|
if err != nil {
|
|
|
|
// Failure to read a random hash results in the proposed
|
|
|
|
// entry not being added to the cache since we are
|
|
|
|
// unable to evict any existing entries.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to find the first entry that is greater than the random
|
|
|
|
// hash. Use the first entry (which is already pseudo random due
|
|
|
|
// to Go's range statement over maps) as a fall back if none of
|
|
|
|
// the hashes in the rejected transactions pool are larger than
|
|
|
|
// the random hash.
|
|
|
|
var foundEntry sigInfo
|
|
|
|
for sigEntry := range s.validSigs {
|
|
|
|
if foundEntry.sig == "" {
|
|
|
|
foundEntry = sigEntry
|
|
|
|
}
|
|
|
|
if bytes.Compare(sigEntry.sigHash.Bytes(), randHashBytes) > 0 {
|
|
|
|
foundEntry = sigEntry
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(s.validSigs, foundEntry)
|
|
|
|
}
|
|
|
|
|
|
|
|
info := sigInfo{sigHash, string(sig.Serialize()),
|
|
|
|
string(pubKey.SerializeCompressed())}
|
|
|
|
s.validSigs[info] = struct{}{}
|
|
|
|
}
|