diff --git a/gcs/bench_test.go b/gcs/bench_test.go index 21e82a77..8fce88bf 100644 --- a/gcs/bench_test.go +++ b/gcs/bench_test.go @@ -17,9 +17,16 @@ var ( // globalHashResult is used to ensure the benchmarks do not elide code. globalHashResult chainhash.Hash +) - // Collision probability for the benchmarks (1/2**20). - P = uint8(20) +const ( + // benchB is used as the B parameter when creating new filters throughout + // the benchmarks. + benchB = uint8(19) + + // benchM is used as the M parameter when creating new filters throughout + // the benchmarks. + benchM = 784931 ) // genFilterElements generates the given number of elements using the provided @@ -51,7 +58,7 @@ func BenchmarkFilterBuild50000(b *testing.B) { b.ResetTimer() var key [KeySize]byte for i := 0; i < b.N; i++ { - _, err := NewFilterV1(P, key, contents) + _, err := NewFilterV2(benchB, benchM, key, contents) if err != nil { b.Fatalf("unable to generate filter: %v", err) } @@ -71,7 +78,7 @@ func BenchmarkFilterBuild100000(b *testing.B) { b.ResetTimer() var key [KeySize]byte for i := 0; i < b.N; i++ { - _, err := NewFilterV1(P, key, contents) + _, err := NewFilterV2(benchB, benchM, key, contents) if err != nil { b.Fatalf("unable to generate filter: %v", err) } @@ -88,7 +95,7 @@ func BenchmarkFilterMatch(b *testing.B) { } var key [KeySize]byte - filter, err := NewFilterV1(P, key, contents) + filter, err := NewFilterV2(benchB, benchM, key, contents) if err != nil { b.Fatalf("Failed to build filter") } @@ -96,7 +103,7 @@ func BenchmarkFilterMatch(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - globalMatch = filter.Match(key, []byte("Nate")) + globalMatch = filter.Match(key, []byte("Something")) globalMatch = filter.Match(key, []byte("Nates")) } } @@ -119,7 +126,7 @@ func BenchmarkFilterMatchAny(b *testing.B) { } var key [KeySize]byte - filter, err := NewFilterV1(P, key, contents) + filter, err := NewFilterV2(benchB, benchM, key, contents) if err != nil { b.Fatalf("Failed to build filter") } @@ -141,7 +148,7 @@ func BenchmarkHash(b *testing.B) { } var key [KeySize]byte - filter, err := NewFilterV1(P, key, contents) + filter, err := NewFilterV2(benchB, benchM, key, contents) if err != nil { b.Fatalf("Failed to build filter") } diff --git a/gcs/error.go b/gcs/error.go index 70e89863..452b6cc0 100644 --- a/gcs/error.go +++ b/gcs/error.go @@ -20,6 +20,10 @@ const ( // collision probability. ErrPTooBig + // ErrBTooBig signifies that the provided Golomb coding bin size is larger + // than the maximum allowed value. + ErrBTooBig + // ErrMisserialized signifies a filter was misserialized and is missing the // N and/or P parameters of a serialized filter. ErrMisserialized @@ -32,6 +36,7 @@ const ( var errorCodeStrings = map[ErrorCode]string{ ErrNTooBig: "ErrNTooBig", ErrPTooBig: "ErrPTooBig", + ErrBTooBig: "ErrBTooBig", ErrMisserialized: "ErrMisserialized", } diff --git a/gcs/error_test.go b/gcs/error_test.go index 6e5b63fd..ae50e7c2 100644 --- a/gcs/error_test.go +++ b/gcs/error_test.go @@ -16,6 +16,7 @@ func TestErrorCodeStringer(t *testing.T) { }{ {ErrNTooBig, "ErrNTooBig"}, {ErrPTooBig, "ErrPTooBig"}, + {ErrBTooBig, "ErrBTooBig"}, {ErrMisserialized, "ErrMisserialized"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/gcs/fastreduce.go b/gcs/fastreduce.go new file mode 100644 index 00000000..09ba7aa2 --- /dev/null +++ b/gcs/fastreduce.go @@ -0,0 +1,39 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +//+build go1.12 + +package gcs + +import ( + "math/bits" +) + +// fastReduce calculates a mapping that is more or less equivalent to x mod N. +// However, instead of using a mod operation that can lead to slowness on many +// processors when not using a power of two due to unnecessary division, this +// uses a "multiply-and-shift" trick that eliminates all divisions as described +// in a blog post by Daniel Lemire, located at the following site at the time +// of this writing: +// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ +// +// Since that link might disappear, the general idea is to multiply by N and +// shift right by log2(N). Since N is a 64-bit integer in this case, it +// becomes: +// +// (x * N) / 2^64 == (x * N) >> 64 +// +// This is a fair map since it maps integers in the range [0,2^64) to multiples +// of N in [0, N*2^64) and then divides by 2^64 to map all multiples of N in +// [0,2^64) to 0, all multiples of N in [2^64, 2*2^64) to 1, etc. This results +// in either ceil(2^64/N) or floor(2^64/N) multiples of N. +func fastReduce(x, N uint64) uint64 { + // This uses math/bits to perform the 128-bit multiplication as the compiler + // will replace it with the relevant intrinsic on most architectures. + // + // The high 64 bits in a 128-bit product is the same as shifting the entire + // product right by 64 bits. + hi, _ := bits.Mul64(x, N) + return hi +} diff --git a/gcs/fastreduce_old.go b/gcs/fastreduce_old.go new file mode 100644 index 00000000..d726c68c --- /dev/null +++ b/gcs/fastreduce_old.go @@ -0,0 +1,53 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +//+build !go1.12 + +package gcs + +// mul64 returns the 128-bit product of x and y: (hi, lo) = x * y +// with the product bits' upper half returned in hi and the lower +// half returned in lo. +// +// Copied from go1.12 stdlib. +func mul64(x, y uint64) (hi, lo uint64) { + const mask32 = 1<<32 - 1 + x0 := x & mask32 + x1 := x >> 32 + y0 := y & mask32 + y1 := y >> 32 + w0 := x0 * y0 + t := x1*y0 + w0>>32 + w1 := t & mask32 + w2 := t >> 32 + w1 += x0 * y1 + hi = x1*y1 + w2 + w1>>32 + lo = x * y + return +} + +// fastReduce calculates a mapping that is more or less equivalent to x mod N. +// However, instead of using a mod operation that can lead to slowness on many +// processors when not using a power of two due to unnecessary division, this +// uses a "multiply-and-shift" trick that eliminates all divisions as described +// in a blog post by Daniel Lemire, located at the following site at the time +// of this writing: +// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ +// +// Since that link might disappear, the general idea is to multiply by N and +// shift right by log2(N). Since N is a 64-bit integer in this case, it +// becomes: +// +// (x * N) / 2^64 == (x * N) >> 64 +// +// This is a fair map since it maps integers in the range [0,2^64) to multiples +// of N in [0, N*2^64) and then divides by 2^64 to map all multiples of N in +// [0,2^64) to 0, all multiples of N in [2^64, 2*2^64) to 1, etc. This results +// in either ceil(2^64/N) or floor(2^64/N) multiples of N. +func fastReduce(x, N uint64) uint64 { + // The high 64 bits in a 128-bit product is the same as shifting the entire + // product right by 64 bits. + hi, _ := mul64(x, N) + return hi +} diff --git a/gcs/gcs.go b/gcs/gcs.go index a73a5e0c..cda9563c 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -7,6 +7,7 @@ package gcs import ( + "bytes" "encoding/binary" "fmt" "math" @@ -16,8 +17,24 @@ import ( "github.com/dchest/siphash" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/crypto/blake256" + "github.com/decred/dcrd/wire" ) +// modReduceV1 is the reduction method used in version 1 filters and simply +// consists of x mod N. +func modReduceV1(x, N uint64) uint64 { + return x % N +} + +// chooseReduceFunc chooses which reduction method to use based on the filter +// version and returns the appropriate function to perform it. +func chooseReduceFunc(version uint16) func(uint64, uint64) uint64 { + if version == 1 { + return modReduceV1 + } + return fastReduce +} + // KeySize is the size of the byte array required for key material for the // SipHash keyed hash function. const KeySize = 16 @@ -34,7 +51,7 @@ func (s *uint64s) Swap(i, j int) { (*s)[i], (*s)[j] = (*s)[j], (*s)[i] } // // It is used internally to implement the exported filter version types. // -// See FilterV1 for more details. +// See FilterV1 and FilterV2 for more details. type filter struct { version uint16 n uint32 @@ -75,11 +92,14 @@ func newFilter(version uint16, B uint8, M uint64, key [KeySize]byte, data [][]by panic(fmt.Sprintf("B value of %d is greater than max allowed 32", B)) } switch version { - case 1: + case 1, 2: default: panic(fmt.Sprintf("version %d filters are not supported", version)) } + // Note that some of the entries might end up hashing to duplicates and + // being removed below, so it is important to perform this check on the + // raw data to maintain consensus. numEntries := uint64(len(data)) if numEntries > math.MaxInt32 { str := fmt.Sprintf("unable to create filter with %d entries greater "+ @@ -87,6 +107,31 @@ func newFilter(version uint16, B uint8, M uint64, key [KeySize]byte, data [][]by return nil, makeError(ErrNTooBig, str) } + // Insert the hash of each data element into a slice while removing any + // duplicates in the data for filters after version 1. + k0 := binary.LittleEndian.Uint64(key[0:8]) + k1 := binary.LittleEndian.Uint64(key[8:16]) + values := make([]uint64, 0, numEntries) + switch { + case version == 1: + for _, d := range data { + v := siphash.Hash(k0, k1, d) + values = append(values, v) + } + + case version > 1: + seen := make(map[uint64]struct{}, numEntries*2) + for _, d := range data { + v := siphash.Hash(k0, k1, d) + if _, ok := seen[v]; ok { + continue + } + seen[v] = struct{}{} + values = append(values, v) + } + } + numEntries = uint64(len(values)) + // Create the filter object and insert metadata. modBMask := uint64(1< 32 { + str := fmt.Sprintf("B value of %d is greater than max allowed 32", B) + return nil, makeError(ErrBTooBig, str) + } + + filter, err := newFilter(2, B, M, key, data) + if err != nil { + return nil, err + } + return &FilterV2{filter: *filter}, nil +} + +// FromBytesV2 deserializes a version 2 GCS filter from a known B, M, and +// serialized filter as returned by Bytes(). +func FromBytesV2(B uint8, M uint64, d []byte) (*FilterV2, error) { + if B > 32 { + str := fmt.Sprintf("B value of %d is greater than max allowed 32", B) + return nil, makeError(ErrBTooBig, str) + } + + var n uint64 + var filterData []byte + if len(d) > 0 { + var err error + n, err = wire.ReadVarInt(bytes.NewReader(d), 0) + if err != nil { + str := fmt.Sprintf("failed to read number of filter items: %v", err) + return nil, makeError(ErrMisserialized, str) + } + filterData = d[wire.VarIntSerializeSize(n):] + } + + f := filter{ + version: 2, + n: uint32(n), + b: B, + modulusNM: n * M, + filterNData: d, + filterData: filterData, + } + return &FilterV2{filter: f}, nil +} + // MakeHeaderForFilter makes a filter chain header for a filter, given the // filter and the previous filter chain header. func MakeHeaderForFilter(filter *FilterV1, prevHeader *chainhash.Hash) chainhash.Hash { diff --git a/gcs/gcs_test.go b/gcs/gcs_test.go index febe9590..c3070081 100644 --- a/gcs/gcs_test.go +++ b/gcs/gcs_test.go @@ -16,6 +16,13 @@ import ( "github.com/decred/dcrd/chaincfg/chainhash" ) +// filterMatcher allows different versions of the filter types to be used for +// match testing. +type filterMatcher interface { + Match([KeySize]byte, []byte) bool + MatchAny([KeySize]byte, [][]byte) bool +} + // TestFilter ensures the filters and all associated methods work as expected by // using various known parameters and contents along with random keys for // matching purposes. @@ -115,6 +122,72 @@ func TestFilter(t *testing.T) { fixedKey: [KeySize]byte{}, wantBytes: "000000111ca3aafb023074dc5bf2498df791b7d6e846e9f5016006d600", wantHash: "afa181cd5c4b08eb9c16d1c97c95df1ca7b82e5e444a396cec5e02f2804fbd1a", + }, { + name: "v2 empty filter", + version: 2, + b: 19, + m: 784931, + matchKey: randKey, + contents: nil, + wantMatches: nil, + fixedKey: [KeySize]byte{}, + wantBytes: "", + wantHash: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "v2 filter contents1 with B=19, M=784931", + version: 2, + b: 19, + m: 784931, + matchKey: randKey, + contents: contents1, + wantMatches: contents1, + fixedKey: [KeySize]byte{}, + wantBytes: "1189af70ad5baf9da83c64e99b18e96a06cd7295a58b324e81f09c85d093f1e33dcd6f40f18cfcbe2aeb771d8390", + wantHash: "b616838c6090d3e732e775cc2f336ce0b836895f3e0f22d6c3ee4485a6ea5018", + }, { + name: "v2 filter contents2 with B=19, M=784931", + version: 2, + b: 19, + m: 784931, + matchKey: randKey, + contents: contents2, + wantMatches: contents2, + fixedKey: [KeySize]byte{}, + wantBytes: "118d4be5372d2f4731c7e1681aefd23028be12306b4d90701a46b472ee80ad60f9fa86c4d6430cfb495ced604362", + wantHash: "f3028f42909209120c8bf649fbbc5a70fb907d8997a02c2c1f2eef0e6402cb15", + }, { + name: "v2 filter contents1 with B=20, M=1569862", + version: 2, + b: 20, + m: 1569862, + matchKey: randKey, + contents: contents1, + wantMatches: contents1, + fixedKey: [KeySize]byte{}, + wantBytes: "1189af7056adebe769078c9e99b1774b509b35c52b4b0b324f40f83f2174227e3c33dcd67a078c33f2f855d6ef1d8390", + wantHash: "10d6c29ba756301e42b97a103de601f604f1cd3150e44ac7cb8cdf63222d93d3", + }, { + name: "v2 filter contents2 with B=20, M=1569862", + version: 2, + b: 20, + m: 1569862, + matchKey: randKey, + contents: contents2, + wantMatches: contents2, + fixedKey: [KeySize]byte{}, + wantBytes: "118d4be69b968bd1cc38fc2d01aefc918142f847e0d69b9070192359fcbba015ac1f9fa86a26b1fc33ed32b9da604361", + wantHash: "cb23a266bf136103cbfedb33041d4f4324106ed1539b9ec5d1cb4e71bc3cbdec", + }, { + name: "v2 filter contents2 with B=10, M=1534", + version: 2, + b: 10, + m: 1534, + matchKey: randKey, + contents: contents2, + wantMatches: contents2, + fixedKey: [KeySize]byte{}, + wantBytes: "118d5a6fbd3e3fc1aa472f983790848fcbcd6b9fb89cc34caf6048", + wantHash: "d7c5f91d6490ec4d66e6db825ad1a628ec7367dc9a47c31bcde31eb5a6ba194b", }} for _, test := range tests { @@ -140,7 +213,8 @@ func TestFilter(t *testing.T) { resultN, uint32(len(test.contents))) continue } - if test.version == 1 { + switch test.version { + case 1: v1Filter := &FilterV1{filter: *f} resultP := v1Filter.P() if resultP != test.b { @@ -148,6 +222,15 @@ func TestFilter(t *testing.T) { resultP, test.b) continue } + + case 2: + v2Filter := &FilterV2{filter: *f} + resultB := v2Filter.B() + if resultB != test.b { + t.Errorf("%q: unexpected B -- got %d, want %d", test.name, + resultB, test.b) + continue + } } // Ensure empty data never matches. @@ -250,12 +333,6 @@ func TestFilter(t *testing.T) { continue } - // filterMatcher allows different versions of the filter types to be - // used for the match testing below. - type filterMatcher interface { - Match([KeySize]byte, []byte) bool - } - // Deserialize the filter from bytes. var f2 filterMatcher switch test.version { @@ -267,6 +344,14 @@ func TestFilter(t *testing.T) { } f2 = tf2 + case 2: + tf2, err := FromBytesV2(test.b, test.m, wantBytes) + if err != nil { + t.Errorf("%q: unexpected err: %v", test.name, err) + continue + } + f2 = tf2 + default: t.Errorf("%q: unsupported filter version: %d", test.name, test.version) @@ -283,16 +368,10 @@ func TestFilter(t *testing.T) { } } -// TestFilterMisses ensures the filter does not match entries with a rate that -// far exceeds the false positive rate. -func TestFilterMisses(t *testing.T) { - // Create a filter with the lowest supported false positive rate to reduce - // the chances of a false positive as much as possible. - var key [KeySize]byte - f, err := NewFilterV1(32, key, [][]byte{[]byte("entry")}) - if err != nil { - t.Fatalf("unexpected err: %v", err) - } +// testFilterMisses ensures the provided filter does not match entries with a +// rate that far exceeds the false positive rate. +func testFilterMisses(t *testing.T, key [KeySize]byte, f filterMatcher) { + t.Helper() // Since the filter may have false positives, try several queries and track // how many matches there are. Something is very wrong if the filter @@ -324,10 +403,33 @@ func TestFilterMisses(t *testing.T) { } } +// TestFilterMisses ensures the filter does not match entries with a rate that +// far exceeds the false positive rate. +func TestFilterMisses(t *testing.T) { + // Create a version 1 filter with the lowest supported false positive rate + // to reduce the chances of a false positive as much as possible and test + // it for misses. + const b = 32 + var key [KeySize]byte + f, err := NewFilterV1(b, key, [][]byte{[]byte("entry")}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + testFilterMisses(t, key, f) + + // Do the same for a version 2 filter. + const m = 6430154453 + f2, err := NewFilterV2(b, m, key, [][]byte{[]byte("entry")}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + testFilterMisses(t, key, f2) +} + // TestFilterCorners ensures a few negative corner cases such as specifying // parameters that are too large behave as expected. func TestFilterCorners(t *testing.T) { - // Attempt to construct filter with parameters too large. + // Attempt to construct v1 filter with parameters too large. const largeP = 33 var key [KeySize]byte _, err := NewFilterV1(largeP, key, nil) @@ -341,7 +443,21 @@ func TestFilterCorners(t *testing.T) { err, ErrPTooBig) } - // Attempt to decode a filter without the N value serialized properly. + // Attempt to construct v2 filter with parameters too large. + const largeB = 33 + const smallM = 1 << 10 + _, err = NewFilterV2(largeB, smallM, key, nil) + if !IsErrorCode(err, ErrBTooBig) { + t.Fatalf("did not receive expected err for B too big -- got %v, want %v", + err, ErrBTooBig) + } + _, err = FromBytesV2(largeB, smallM, nil) + if !IsErrorCode(err, ErrBTooBig) { + t.Fatalf("did not receive expected err for B too big -- got %v, want %v", + err, ErrBTooBig) + } + + // Attempt to decode a v1 filter without the N value serialized properly. _, err = FromBytesV1(20, []byte{0x00}) if !IsErrorCode(err, ErrMisserialized) { t.Fatalf("did not receive expected err -- got %v, want %v", err, diff --git a/gcs/go.sum b/gcs/go.sum index acff25df..be0140a6 100644 --- a/gcs/go.sum +++ b/gcs/go.sum @@ -16,8 +16,6 @@ github.com/decred/base58 v1.0.0 h1:BVi1FQCThIjZ0ehG+I99NJ51o0xcc9A/fDKhmJxY6+w= github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks= github.com/decred/dcrd/blockchain/stake/v2 v2.0.0 h1:+FMrSt5tPicBKlev0k/r/2VsaVwpIUcm1TPw69XgZw0= github.com/decred/dcrd/blockchain/stake/v2 v2.0.0/go.mod h1:jv/rKMcZ87lhvVkHot/tElxeAYEUJ3mnKPHJ7WPq86U= -github.com/decred/dcrd/chaincfg v1.5.1 h1:u1Xbq0VTnAXIHW5ECqrWe0VYSgf5vWHqpSiwoLBzxAQ= -github.com/decred/dcrd/chaincfg v1.5.1/go.mod h1:FukMzTjkwzjPU+hK7CqDMQe3NMbSZAYU5PAcsx1wlv0= github.com/decred/dcrd/chaincfg/chainhash v1.0.1 h1:0vG7U9+dSjSCaHQKdoSKURK2pOb47+b+8FK5q4+Je7M= github.com/decred/dcrd/chaincfg/chainhash v1.0.1/go.mod h1:OVfvaOsNLS/A1y4Eod0Ip/Lf8qga7VXCQjUQLbkY0Go= github.com/decred/dcrd/chaincfg/v2 v2.0.2 h1:VeGY52lHuYT01tIGbvYj+OO0GaGxGaJmnh+4vGca1+U= @@ -26,36 +24,20 @@ github.com/decred/dcrd/chaincfg/v2 v2.1.0 h1:2S7TL9YWnKDDiH5bTpp3xcBo+1gl1IXFi5K github.com/decred/dcrd/chaincfg/v2 v2.1.0/go.mod h1:hpKvhLCDAD/xDZ3V1Pqpv9fIKVYYi11DyxETguazyvg= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/database v1.0.1 h1:BSIerNf4RhSA0iDhiE/320RYqD2y9T+SCj99Pv7svgo= -github.com/decred/dcrd/database v1.0.1/go.mod h1:ILCeyOHFew3fZ7K2B9jl+tp5qFOap/pEGoo6Yy6Wk0g= github.com/decred/dcrd/database/v2 v2.0.0 h1:KWiyZHk+QyNKQvvxm/KpIejhTqYJqH9ssz1+9sT9nVA= github.com/decred/dcrd/database/v2 v2.0.0/go.mod h1:Sj2lvTRB0mfSu9uD7ObfwCY/eJ954GFU/X+AndJIyfE= -github.com/decred/dcrd/dcrec v0.0.0-20180721005212-59fe2b293f69/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180721031028-5369a485acf6/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= -github.com/decred/dcrd/dcrec v0.0.0-20180801202239-0761de129164 h1:N5s3yVfjBNW6XNG3gLxYpvt0IUjUsp/FRfC75QpSI+E= -github.com/decred/dcrd/dcrec v0.0.0-20180801202239-0761de129164/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o= github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20180721005212-59fe2b293f69/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20180721031028-5369a485acf6/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20181208004914-a0816cf4301f h1:NF7vp3nZ4MsAiXswGmE//m83jCN0lDsQrLI7IwLCTlo= -github.com/decred/dcrd/dcrec/edwards v0.0.0-20181208004914-a0816cf4301f/go.mod h1:+ehP0Hk/mesyZXttxCtBbhPX23BMpZJ1pcVBqUfbmvU= github.com/decred/dcrd/dcrec/edwards v1.0.0 h1:UDcPNzclKiJlWqV3x1Fl8xMCJrolo4PB4X9t8LwKDWU= github.com/decred/dcrd/dcrec/edwards v1.0.0/go.mod h1:HblVh1OfMt7xSxUL1ufjToaEvpbjpWvvTAUx4yem8BI= -github.com/decred/dcrd/dcrec/secp256k1 v1.0.0/go.mod h1:JPMFscGlgXTV684jxQNDijae2qrh0fLG7pJBimaYotE= github.com/decred/dcrd/dcrec/secp256k1 v1.0.1 h1:EFWVd1p0t0Y5tnsm/dJujgV0ORogRJ6vo7CMAjLseAc= github.com/decred/dcrd/dcrec/secp256k1 v1.0.1/go.mod h1:lhu4eZFSfTJWUnR3CFRcpD+Vta0KUAqnhTsTksHXgy0= github.com/decred/dcrd/dcrec/secp256k1 v1.0.2 h1:awk7sYJ4pGWmtkiGHFfctztJjHMKGLV8jctGQhAbKe0= github.com/decred/dcrd/dcrec/secp256k1 v1.0.2/go.mod h1:CHTUIVfmDDd0KFVFpNX1pFVCBUegxW387nN0IGwNKR0= -github.com/decred/dcrd/dcrutil v1.1.1 h1:zOkGiumN/JkobhAgpG/zfFgUoolGKVGYT5na1hbYUoE= -github.com/decred/dcrd/dcrutil v1.1.1/go.mod h1:Jsttr0pEvzPAw+qay1kS1/PsbZYPyhluiNwwY6yBJS4= github.com/decred/dcrd/dcrutil/v2 v2.0.0 h1:HTqn2tZ8eqBF4y3hJwjyKBmJt16y7/HjzpE82E/crhY= github.com/decred/dcrd/dcrutil/v2 v2.0.0/go.mod h1:gUshVAXpd51DlcEhr51QfWL2HJGkMDM1U8chY+9VvQg= -github.com/decred/dcrd/txscript v1.0.1 h1:IMgxZFCw3AyG4EbKwywE3SDNshOSHsoUK1Wk/5GqWJ0= -github.com/decred/dcrd/txscript v1.0.1/go.mod h1:FqUX07Y+u3cJ1eIGPoyWbJg+Wk1NTllln/TyDpx9KnY= github.com/decred/dcrd/txscript/v2 v2.0.0 h1:So+NcQY58mDHDN2N2edED5syGZp2ed8Ltxj8mDE5CAs= github.com/decred/dcrd/txscript/v2 v2.0.0/go.mod h1:WStcyYYJa+PHJB4XjrLDRzV96/Z4thtsu8mZoVrU6C0= -github.com/decred/dcrd/wire v1.1.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM= github.com/decred/dcrd/wire v1.2.0 h1:HqJVB7vcklIguzFWgRXw/WYCQ9cD3bUC5TKj53i1Hng= github.com/decred/dcrd/wire v1.2.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM= github.com/decred/slog v1.0.0 h1:Dl+W8O6/JH6n2xIFN2p3DNjCmjYwvrXsjlSJTQQ4MhE= @@ -71,20 +53,12 @@ github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -golang.org/x/crypto v0.0.0-20180718160520-a2144134853f h1:lRy+hhwk7YT7MsKejxuz0C5Q1gk6p/QoPQYEmKmGFb8= -golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 h1:mEsFm194MmS9vCwxFy+zwu0EU7ZkxxMD1iH++vmGdUY= -golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c h1:uHnKXcvx6SNkuwC+nrzxkJ+TpPwZOtumbhWrrOYN5YA= -golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=