├── common.go ├── LICENSE.md ├── README.md ├── basic_pairwise.go ├── mkmoduli └── main.go ├── mul_accum.go ├── crttomoduli └── main.go ├── smoothparts.go ├── batchgcd ├── main.go └── output-sorted.txt └── smoothparts_lowmem.go /common.go: -------------------------------------------------------------------------------- 1 | package batchgcd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ncw/gmp" 6 | ) 7 | 8 | type Collision struct { 9 | Modulus *gmp.Int 10 | P *gmp.Int 11 | Q *gmp.Int 12 | } 13 | 14 | func (x Collision) HavePrivate() bool { 15 | return x.P != nil || x.Q != nil 16 | } 17 | 18 | func (x Collision) String() string { 19 | if x.HavePrivate() { 20 | if x.P.Cmp(x.Q) < 0 { 21 | return fmt.Sprintf("COLLISION: N=%x P=%x Q=%x", x.Modulus, x.P, x.Q) 22 | } else { 23 | return fmt.Sprintf("COLLISION: N=%x P=%x Q=%x", x.Modulus, x.Q, x.P) 24 | } 25 | } else { 26 | return fmt.Sprintf("DUPLICATE: %x", x.Modulus) 27 | } 28 | } 29 | 30 | func (x Collision) Test() bool { 31 | if !x.HavePrivate() { 32 | return true 33 | } 34 | n := gmp.NewInt(0) 35 | n.Mul(x.P, x.Q) 36 | return n.Cmp(x.Modulus) == 0 37 | } 38 | 39 | func (x Collision) Csv() string { 40 | if x.P.Cmp(x.Q) < 0 { 41 | return fmt.Sprintf("%x,%x,%x", x.Modulus, x.P, x.Q) 42 | } else { 43 | return fmt.Sprintf("%x,%x,%x", x.Modulus, x.Q, x.P) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Samuel (except where otherwise noted in source files) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BatchGCD 2 | Go Library (and program) to perform pairwise gcd on large number of RSA moduli 3 | 4 | This implements three different ways to perform pairwise GCD on a large number of RSA moduli. 5 | - Actual pairwise GCD 6 | 7 | This performs n*(n-1)/2 GCD operations on the moduli. This is slow. 8 | Don't use this. 9 | 10 | - Accumulating Product 11 | 12 | This iterates over all input moduli, performing a GCD of each one against the product of all previous. 13 | Once it finds a candidate, it scans all previous moduli to find out which ones it shared a factor with 14 | (either GCD or division, depending on whether one or both were found). 15 | The main scan cannot be done in parallel, and even though it seems like this is O(n), the increasing size 16 | of the accumulated product results it lots of long multiplication and long divison so it's still painfully 17 | slow for large numbers of moduli. 18 | 19 | - Smooth Parts 20 | 21 | DJB's "How to find smooth parts of integers" http://cr.yp.to/papers.html#smoothparts 22 | This creates a product tree, then converts it to a remainder tree, then kablam you find common factors. 23 | This is largely the same as the one at https://factorable.net/resources.html 24 | This is the default, use this unless you're trying to burn in a new CPU or something. 25 | -------------------------------------------------------------------------------- /basic_pairwise.go: -------------------------------------------------------------------------------- 1 | package batchgcd 2 | 3 | import ( 4 | "github.com/ncw/gmp" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | func BasicPairwiseGCD(moduli []*gmp.Int, collisions chan<- Collision) { 10 | var wg sync.WaitGroup 11 | nThreads := runtime.NumCPU() 12 | 13 | wg.Add(nThreads) 14 | for i := 0; i < nThreads; i++ { 15 | go pairwiseThread(i, nThreads, &wg, moduli, collisions) 16 | } 17 | wg.Wait() 18 | close(collisions) 19 | } 20 | 21 | func pairwiseThread(start, step int, wg *sync.WaitGroup, moduli []*gmp.Int, collisions chan<- Collision) { 22 | gcd := gmp.NewInt(0) 23 | 24 | for i := start; i < len(moduli); i += step { 25 | for j := i + 1; j < len(moduli); j++ { 26 | m1 := moduli[i] 27 | m2 := moduli[j] 28 | if m1.Cmp(m2) == 0 { 29 | collisions <- Collision{Modulus: m1} 30 | } else if gcd.GCD(nil, nil, m1, m2).BitLen() != 1 { // There's only one number with a BitLen of 1 31 | q := gmp.NewInt(0) 32 | q.Quo(m1, gcd) 33 | collisions <- Collision{ 34 | Modulus: m1, 35 | P: gcd, 36 | Q: q, 37 | } 38 | 39 | q = gmp.NewInt(0) 40 | q.Quo(m2, gcd) 41 | collisions <- Collision{ 42 | Modulus: m2, 43 | P: gcd, 44 | Q: q, 45 | } 46 | 47 | gcd = gmp.NewInt(0) // Old gcd var can't be overwritten 48 | } 49 | } 50 | } 51 | wg.Done() 52 | } 53 | -------------------------------------------------------------------------------- /mkmoduli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | cryptorand "crypto/rand" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | "os" 10 | "runtime" 11 | "sync" 12 | ) 13 | 14 | var dupeprob = flag.Int("prob", 1000, "1/n integers will reuse a modulus") 15 | var nummoduli = flag.Int("num", 100000, "How many moduli to generate") 16 | var bits = flag.Int("bits", 2048, "Bits per RSA modulus") 17 | 18 | func main() { 19 | runtime.GOMAXPROCS(runtime.NumCPU()) 20 | log.SetOutput(os.Stderr) 21 | flag.Parse() 22 | 23 | numModuli := *nummoduli 24 | numThreads := runtime.NumCPU() 25 | perThread := (numModuli + numThreads - 1) / numThreads 26 | var wg sync.WaitGroup 27 | ch := make(chan *big.Int, numThreads) 28 | 29 | for numModuli > 0 { 30 | if perThread > numModuli { 31 | perThread = numModuli 32 | } 33 | wg.Add(1) 34 | go genModuli(perThread, ch, &wg) 35 | numModuli -= perThread 36 | } 37 | go func() { 38 | wg.Wait() 39 | close(ch) 40 | }() 41 | for modulus := range ch { 42 | fmt.Printf("%x\n", modulus) 43 | } 44 | } 45 | 46 | func genModuli(numModuli int, output chan<- *big.Int, wg *sync.WaitGroup) { 47 | dupChan := make(chan *big.Int, 1) 48 | var prime1, prime2 *big.Int 49 | var err error 50 | 51 | for i := 0; i < numModuli; i++ { 52 | prime1, err = cryptorand.Prime(cryptorand.Reader, (*bits+1)/2) 53 | if err != nil { 54 | log.Fatal("Unable to generate random prime") 55 | } 56 | if (i % (*dupeprob)) == 1 { 57 | select { 58 | case prime2 = <-dupChan: 59 | output <- new(big.Int).Mul(prime1, prime2) 60 | continue 61 | default: 62 | dupChan <- prime1 63 | } 64 | } 65 | prime2, err = cryptorand.Prime(cryptorand.Reader, (*bits)/2) 66 | if err != nil { 67 | log.Fatal("Unable to generate random prime") 68 | } 69 | output <- new(big.Int).Mul(prime1, prime2) 70 | } 71 | wg.Done() 72 | } 73 | -------------------------------------------------------------------------------- /mul_accum.go: -------------------------------------------------------------------------------- 1 | package batchgcd 2 | 3 | import ( 4 | "github.com/ncw/gmp" 5 | "sync" 6 | ) 7 | 8 | // This performs the GCD of the product of all previous moduli with the current one. 9 | // This uses around double the memory (minus quite a lot of overhead), and identifies 10 | // problematic input in O(n) time, but has to do another O(n) scan for each collision 11 | // to figure get the private key back. 12 | // If there are no collisions, this algorithm isn't parallel at all. 13 | // If we get a GCD that is the same as the modulus, we do a manual scan for either colliding Q or identical moduli 14 | // If we get a GCD lower than the modulus, we have one private key, then do a manual scan for others. 15 | func MulAccumGCD(moduli []*gmp.Int, collisions chan<- Collision) { 16 | accum := gmp.NewInt(1) 17 | gcd := new(gmp.Int) 18 | var wg sync.WaitGroup 19 | 20 | for i, modulus := range moduli { 21 | gcd.GCD(nil, nil, accum, modulus) 22 | if gcd.BitLen() != 1 { 23 | wg.Add(1) 24 | if gcd.Cmp(modulus) == 0 { 25 | go findGCD(&wg, moduli, i, collisions) 26 | continue 27 | } else { 28 | go findDivisors(&wg, moduli, i, gcd, collisions) 29 | gcd = new(gmp.Int) 30 | } 31 | } 32 | accum.Mul(accum, modulus) 33 | } 34 | wg.Wait() 35 | close(collisions) 36 | } 37 | 38 | // Tests the candidate (i) against all other moduli 39 | func findDivisors(wg *sync.WaitGroup, moduli []*gmp.Int, i int, gcd *gmp.Int, collisions chan<- Collision) { 40 | m := moduli[i] 41 | q := gmp.NewInt(0) 42 | r := gmp.NewInt(0) 43 | 44 | q.Quo(m, gcd) 45 | collisions <- Collision{ 46 | Modulus: m, 47 | P: gcd, 48 | Q: q, 49 | } 50 | q = gmp.NewInt(0) 51 | 52 | for j := 0; j < i; j++ { 53 | n := moduli[j] 54 | q.QuoRem(n, gcd, r) 55 | if r.BitLen() == 0 { 56 | collisions <- Collision{ 57 | Modulus: n, 58 | P: gcd, 59 | Q: q, 60 | } 61 | } 62 | q = gmp.NewInt(0) 63 | } 64 | wg.Done() 65 | } 66 | 67 | func findGCD(wg *sync.WaitGroup, moduli []*gmp.Int, i int, collisions chan<- Collision) { 68 | m := moduli[i] 69 | q := gmp.NewInt(0) 70 | gcd := gmp.NewInt(0) 71 | 72 | for j := 0; j < i; j++ { 73 | n := moduli[j] 74 | 75 | if gcd.GCD(nil, nil, m, n).BitLen() != 1 { 76 | q.Quo(m, gcd) 77 | collisions <- Collision{ 78 | Modulus: m, 79 | P: gcd, 80 | Q: q, 81 | } 82 | q = gmp.NewInt(0) 83 | 84 | q.Quo(n, gcd) 85 | collisions <- Collision{ 86 | Modulus: n, 87 | P: gcd, 88 | Q: q, 89 | } 90 | q = gmp.NewInt(0) 91 | 92 | gcd = gmp.NewInt(0) 93 | } 94 | } 95 | wg.Done() 96 | } 97 | -------------------------------------------------------------------------------- /crttomoduli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rsa" 6 | "github.com/therealmik/x509" 7 | "encoding/base64" 8 | "encoding/pem" 9 | "flag" 10 | "fmt" 11 | "io/ioutil" 12 | "log" 13 | "math/big" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | var zmapFile = flag.Bool("zmap", false, "Data is in zmap format") 19 | 20 | func main() { 21 | log.SetOutput(os.Stderr) 22 | flag.Parse() 23 | if len(flag.Args()) == 0 { 24 | log.Fatal("No files specified") 25 | } 26 | 27 | ch := make(chan []byte) 28 | 29 | go printModuli(ch) 30 | for _, filename := range flag.Args() { 31 | log.Print("Loading moduli from ", filename) 32 | if *zmapFile { 33 | readZmap(filename, ch) 34 | } else { 35 | readPem(filename, ch) 36 | } 37 | } 38 | } 39 | 40 | func readPem(filename string, ch chan<- []byte) { 41 | data, err := ioutil.ReadFile(filename) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | for len(data) > 0 { 47 | var block *pem.Block 48 | block, data = pem.Decode(data) 49 | if block == nil { 50 | break 51 | } 52 | if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { 53 | continue 54 | } 55 | 56 | ch <- block.Bytes 57 | } 58 | } 59 | 60 | func readZmap(filename string, ch chan<- []byte) { 61 | fd, err := os.Open(filename) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | scanner := bufio.NewScanner(fd) 66 | var lineNumber int 67 | for scanner.Scan() { 68 | lineNumber += 1 69 | fields := strings.Split(scanner.Text(), ",") 70 | if len(fields) != 2 { 71 | log.Fatalf("Malformed line in %s:%d (should be exactly 1 comma per line), got %v", filename, lineNumber, fields) 72 | } 73 | data, err := base64.StdEncoding.DecodeString(fields[1]) 74 | if err != nil { 75 | log.Fatalf("Malformed base64 in %s:%d: %v", filename, lineNumber, err) 76 | } 77 | ch <- data 78 | } 79 | } 80 | 81 | func printModuli(ch <-chan []byte) { 82 | smallest := big.NewInt(65537) 83 | for blob := range ch { 84 | cert, err := x509.ParseCertificate(blob) 85 | if err != nil { 86 | log.Printf("Error in cert %v: %s", err, base64.StdEncoding.EncodeToString(blob)) 87 | continue 88 | } 89 | if cert.PublicKeyAlgorithm != x509.RSA { 90 | log.Printf("Skipping non-RSA certificate") 91 | continue 92 | } 93 | pk := cert.PublicKey.(*rsa.PublicKey) 94 | if pk.N.Cmp(smallest) < 1 { 95 | log.Printf("Skipping small/negative modulus") 96 | continue 97 | } 98 | fmt.Printf("%x,%s\n", pk.N, base64.StdEncoding.EncodeToString(cert.Raw)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /smoothparts.go: -------------------------------------------------------------------------------- 1 | package batchgcd 2 | 3 | // NOTE: This code was written with fastgcd available at https://factorable.net/ 4 | // as a reference, which was written by Nadia Heninger and J. Alex Halderman. 5 | // I have put a substantial amount of my own design into this, and they do not 6 | // claim it as a derivative work. 7 | // I thank them for their original code and paper. 8 | 9 | import ( 10 | "github.com/ncw/gmp" 11 | "runtime" 12 | "sync" 13 | ) 14 | 15 | // Multiply sets of two adjacent inputs, placing into a single output 16 | func productTreeLevel(input []*gmp.Int, output []*gmp.Int, wg *sync.WaitGroup, start, step int) { 17 | for i := start; i < (len(input) / 2); i += step { 18 | j := i * 2 19 | output[i] = new(gmp.Int).Mul(input[j], input[j+1]) 20 | } 21 | wg.Done() 22 | } 23 | 24 | // For each productTree node 'x', and remainderTree parent 'y', compute y%(x*x) 25 | func remainderTreeLevel(tree [][]*gmp.Int, level int, wg *sync.WaitGroup, start, step int) { 26 | prevLevel := tree[level+1] 27 | thisLevel := tree[level] 28 | tmp := new(gmp.Int) 29 | 30 | for i := start; i < len(thisLevel); i += step { 31 | x := thisLevel[i] 32 | y := prevLevel[i/2] 33 | tmp.Mul(x, x) 34 | x.Rem(y, tmp) 35 | } 36 | wg.Done() 37 | } 38 | 39 | // For each input modulus 'x' and remainderTree parent 'y', compute z = (y%(x*x))/x; gcd(z, x) 40 | func remainderTreeFinal(lastLevel, moduli []*gmp.Int, output chan<- Collision, wg *sync.WaitGroup, start, step int) { 41 | tmp := new(gmp.Int) 42 | 43 | for i := start; i < len(moduli); i += step { 44 | modulus := moduli[i] 45 | y := lastLevel[i/2] 46 | tmp.Mul(modulus, modulus) 47 | tmp.Rem(y, tmp) 48 | tmp.Quo(tmp, modulus) 49 | if tmp.GCD(nil, nil, tmp, modulus).BitLen() != 1 { 50 | q := new(gmp.Int).Quo(modulus, tmp) 51 | output <- Collision{ 52 | Modulus: modulus, 53 | P: tmp, 54 | Q: q, 55 | } 56 | tmp = new(gmp.Int) 57 | } 58 | } 59 | wg.Done() 60 | } 61 | 62 | // Implementation of D.J. Bernstein's "How to find smooth parts of integers" 63 | // http://cr.yp.to/papers.html#smoothparts 64 | func SmoothPartsGCD(moduli []*gmp.Int, output chan<- Collision) { 65 | defer close(output) 66 | if len(moduli) < 2 { 67 | return 68 | } 69 | 70 | // Create a tree, each level being ceil(n/2) in size, where 71 | // n is the size of the level below. The top level is size 1, 72 | // the bottom level is the input moduli 73 | tree := make([][]*gmp.Int, 0) 74 | for n := (len(moduli) + 1) / 2; ; n = (n + 1) / 2 { 75 | tree = append(tree, make([]*gmp.Int, n)) 76 | if n == 1 { 77 | break 78 | } 79 | } 80 | 81 | var wg sync.WaitGroup 82 | nThreads := runtime.NumCPU() 83 | 84 | // Create a product tree 85 | input := moduli 86 | for level := 0; level < len(tree); level++ { 87 | output := tree[level] 88 | 89 | wg.Add(nThreads) 90 | for i := 0; i < nThreads; i++ { 91 | go productTreeLevel(input, output, &wg, i, nThreads) 92 | } 93 | 94 | if (len(input) & 1) == 1 { 95 | output[len(output)-1] = input[len(input)-1] 96 | } 97 | wg.Wait() 98 | 99 | input = output 100 | } 101 | 102 | // Create a remainder tree 103 | for level := len(tree) - 2; level >= 0; level-- { 104 | wg.Add(nThreads) 105 | for i := 0; i < nThreads; i++ { 106 | go remainderTreeLevel(tree, level, &wg, i, nThreads) 107 | } 108 | wg.Wait() 109 | } 110 | 111 | // The final round is a little special, partially because of how my code 112 | // handles it's input, but also because there are extra steps. 113 | wg.Add(nThreads) 114 | for i := 0; i < nThreads; i++ { 115 | go remainderTreeFinal(tree[0], moduli, output, &wg, i, nThreads) 116 | } 117 | wg.Wait() 118 | } 119 | -------------------------------------------------------------------------------- /batchgcd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "github.com/ncw/gmp" 8 | "github.com/therealmik/batchgcd" 9 | "log" 10 | "os" 11 | "runtime" 12 | "runtime/pprof" 13 | "strings" 14 | ) 15 | 16 | const ( 17 | MODULI_BASE = 16 // Hex 18 | GCCOUNT = 250000 19 | ) 20 | 21 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 22 | var algorithmName = flag.String("algorithm", "smoothparts", "mulaccum|pairwise|smoothparts|smoothparts_lowmem") 23 | 24 | func main() { 25 | runtime.GOMAXPROCS(runtime.NumCPU()) 26 | log.SetOutput(os.Stderr) 27 | flag.Parse() 28 | 29 | if len(flag.Args()) == 0 { 30 | log.Fatal("No files specified") 31 | } 32 | 33 | if *cpuprofile != "" { 34 | f, err := os.Create(*cpuprofile) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | pprof.StartCPUProfile(f) 39 | defer pprof.StopCPUProfile() 40 | } 41 | 42 | var f func([]*gmp.Int, chan<- batchgcd.Collision) 43 | 44 | switch *algorithmName { 45 | case "pairwise": 46 | f = batchgcd.BasicPairwiseGCD 47 | case "mulaccum": 48 | f = batchgcd.MulAccumGCD 49 | case "smoothparts": 50 | f = batchgcd.SmoothPartsGCD 51 | case "smoothparts_lowmem": 52 | doLowMem() 53 | return 54 | default: 55 | log.Fatal("Invalid algorithm: ", *algorithmName) 56 | } 57 | 58 | moduli := make([]*gmp.Int, 0) 59 | for _, filename := range flag.Args() { 60 | log.Print("Loading moduli from ", filename) 61 | moduli = loadModuli(moduli, filename) 62 | } 63 | 64 | ch := make(chan batchgcd.Collision, 256) 65 | log.Print("Executing...") 66 | go f(moduli, ch) 67 | 68 | for compromised := range uniqifyCollisions(ch) { 69 | if !compromised.Test() { 70 | log.Fatal("Test failed on ", compromised) 71 | } 72 | fmt.Println(compromised.Csv()) 73 | } 74 | log.Print("Finished.") 75 | } 76 | 77 | func doLowMem() { 78 | moduli := make(chan *gmp.Int, 1) 79 | collisions := make(chan batchgcd.Collision, 1) 80 | 81 | log.Print("Executing...") 82 | go batchgcd.LowMemSmoothPartsGCD(moduli, collisions) 83 | 84 | for _, filename := range flag.Args() { 85 | log.Print("Reading moduli from ", filename) 86 | readModuli(moduli, filename) 87 | log.Print("Done reading moduli from ", filename) 88 | } 89 | close(moduli) 90 | 91 | for compromised := range uniqifyCollisions(collisions) { 92 | if !compromised.Test() { 93 | log.Fatal("Test failed on ", compromised) 94 | } 95 | fmt.Println(compromised.Csv()) 96 | } 97 | log.Print("Finished.") 98 | } 99 | 100 | func loadModuli(moduli []*gmp.Int, filename string) []*gmp.Int { 101 | ch := make(chan *gmp.Int, 1) 102 | go func() { 103 | readModuli(ch, filename) 104 | close(ch) 105 | }() 106 | for m := range ch { 107 | moduli = append(moduli, m) 108 | } 109 | return moduli 110 | } 111 | 112 | func readModuli(ch chan *gmp.Int, filename string) { 113 | fp, err := os.Open(filename) 114 | if err != nil { 115 | log.Fatal(err) 116 | } 117 | defer fp.Close() 118 | defer runtime.GC() 119 | 120 | var count uint64 121 | seen := make(map[string]struct{}) 122 | scanner := bufio.NewScanner(fp) 123 | for scanner.Scan() { 124 | count += 1 125 | if count%GCCOUNT == 0 { 126 | log.Print("Moduli read: ", count) 127 | runtime.GC() 128 | } 129 | m := new(gmp.Int) 130 | 131 | splitModuli := strings.SplitN(scanner.Text(), ",", 2) 132 | s := splitModuli[0] // Accept CSV moduli, so long as modulus is first column 133 | 134 | // Dedupe 135 | if _, ok := seen[s]; ok { 136 | continue 137 | } else { 138 | seen[s] = struct{}{} 139 | } 140 | 141 | if _, ok := m.SetString(s, MODULI_BASE); !ok { 142 | log.Fatal("Invalid modulus in filename ", filename, ": ", scanner.Text()) 143 | } 144 | ch <- m 145 | } 146 | } 147 | 148 | func uniqifyCollisions(in <-chan batchgcd.Collision) chan batchgcd.Collision { 149 | out := make(chan batchgcd.Collision) 150 | go uniqifyProc(in, out) 151 | return out 152 | } 153 | 154 | func uniqifyProc(in <-chan batchgcd.Collision, out chan<- batchgcd.Collision) { 155 | seen := make(map[string]struct{}) 156 | for c := range in { 157 | s := c.String() 158 | if _, ok := seen[s]; ok { 159 | continue 160 | } 161 | seen[s] = struct{}{} 162 | out <- c 163 | } 164 | close(out) 165 | } 166 | -------------------------------------------------------------------------------- /smoothparts_lowmem.go: -------------------------------------------------------------------------------- 1 | package batchgcd 2 | 3 | // NOTE: This code was written with fastgcd available at https://factorable.net/ 4 | // as a reference, which was written by Nadia Heninger and J. Alex Halderman. 5 | // I have put a substantial amount of my own design into this, and they do not 6 | // claim it as a derivative work. 7 | // I thank them for their original code and paper. 8 | 9 | import ( 10 | "github.com/ncw/gmp" 11 | "io" 12 | "io/ioutil" 13 | "log" 14 | "syscall" 15 | ) 16 | 17 | type fileChannels struct { 18 | writeChan chan *gmp.Int 19 | readChan chan *gmp.Int 20 | producing bool 21 | } 22 | 23 | func (fch *fileChannels) StartProducing() { 24 | if fch.producing { 25 | return 26 | } 27 | close(fch.writeChan) 28 | fch.producing = true 29 | } 30 | 31 | func encodeLength(buf []byte, length int) { 32 | buf[0] = byte(length >> 56) 33 | buf[1] = byte(length >> 48) 34 | buf[2] = byte(length >> 40) 35 | buf[3] = byte(length >> 32) 36 | buf[4] = byte(length >> 24) 37 | buf[5] = byte(length >> 16) 38 | buf[6] = byte(length >> 8) 39 | buf[7] = byte(length) 40 | } 41 | 42 | func decodeLength(buf []byte) int { 43 | var ret int 44 | 45 | ret |= int(buf[0]) << 56 46 | ret |= int(buf[1]) << 48 47 | ret |= int(buf[2]) << 40 48 | ret |= int(buf[3]) << 32 49 | ret |= int(buf[4]) << 24 50 | ret |= int(buf[5]) << 16 51 | ret |= int(buf[6]) << 8 52 | ret |= int(buf[7]) 53 | 54 | return ret 55 | } 56 | 57 | func tmpfileReadWriter(ch fileChannels) { 58 | tmpFile, err := ioutil.TempFile(".", "product") 59 | if err != nil { 60 | log.Panic(err) 61 | } 62 | 63 | length := make([]byte, 8) 64 | 65 | var writeCount uint64 66 | for inData := range ch.writeChan { 67 | buf := inData.Bytes() 68 | encodeLength(length, len(buf)) 69 | if _, err := tmpFile.Write(length); err != nil { 70 | log.Panic(err) 71 | } 72 | if _, err := tmpFile.Write(buf); err != nil { 73 | log.Panic(err) 74 | } 75 | inData.Clear() 76 | writeCount += 1 77 | } 78 | 79 | if newOffset, e := tmpFile.Seek(0, 0); e != nil || newOffset != 0 { 80 | log.Panic(e) 81 | } 82 | 83 | var readCount uint64 84 | m := new(gmp.Int) 85 | for { 86 | if _, e := io.ReadFull(tmpFile, length); e != nil { 87 | if e == io.EOF { 88 | break 89 | } 90 | log.Panic(e) 91 | } 92 | buf := make([]byte, decodeLength(length)) 93 | if _, e := io.ReadFull(tmpFile, buf); e != nil { 94 | log.Panic(e) 95 | } 96 | readCount += 1 97 | ch.readChan <- m.SetBytes(buf) 98 | m = new(gmp.Int) 99 | } 100 | 101 | if writeCount != readCount { 102 | log.Panicf("Didn't write as many as we read: write=%v read=%v", writeCount, readCount) 103 | } 104 | close(ch.readChan) 105 | syscall.Unlink(tmpFile.Name()) 106 | // tmpFile.Truncate(0); 107 | } 108 | 109 | // Multiply sets of two adjacent inputs, placing into a single output 110 | func lowmemProductTreeLevel(input chan *gmp.Int, channels []fileChannels, finalOutput chan<- Collision) { 111 | resultChan := make(chan *gmp.Int, 2) 112 | defer close(resultChan) 113 | 114 | hold := <-input 115 | m, ok := <-input 116 | if !ok { 117 | go lowmemRemainderTreeLevel(resultChan, channels, finalOutput) 118 | resultChan <- hold 119 | return 120 | } 121 | 122 | fileChans := fileChannels{ 123 | writeChan: make(chan *gmp.Int, 2), 124 | readChan: make(chan *gmp.Int, 1), 125 | } 126 | go tmpfileReadWriter(fileChans) 127 | channels = append(channels, fileChans) 128 | go lowmemProductTreeLevel(resultChan, channels, finalOutput) 129 | resultChan <- new(gmp.Int).Mul(hold, m) 130 | 131 | fileChans.writeChan <- hold 132 | fileChans.writeChan <- m 133 | 134 | hold = nil 135 | 136 | for m = range input { 137 | if hold != nil { 138 | resultChan <- new(gmp.Int).Mul(hold, m) 139 | fileChans.writeChan <- hold 140 | fileChans.writeChan <- m 141 | hold = nil 142 | } else { 143 | hold = m 144 | } 145 | } 146 | 147 | if hold != nil { 148 | fileChans.writeChan <- hold 149 | resultChan <- hold 150 | } 151 | } 152 | 153 | // For each productTree node 'x', and remainderTree parent 'y', compute y%(x*x) 154 | func lowmemRemainderTreeLevel(input chan *gmp.Int, productTree []fileChannels, finalOutput chan<- Collision) { 155 | tmp := new(gmp.Int) 156 | 157 | ch := productTree[len(productTree)-1] 158 | productTree = productTree[:len(productTree)-1] 159 | 160 | // We close the fileWriteChannel here so it kicks off reading now, instead of starting too early 161 | products := ch.readChan 162 | 163 | output := make(chan *gmp.Int, 2) 164 | defer close(output) 165 | 166 | if len(productTree) == 0 { 167 | ch.StartProducing() 168 | lowmemRemainderTreeFinal(input, products, finalOutput) 169 | return 170 | } else { 171 | go lowmemRemainderTreeLevel(output, productTree, finalOutput) 172 | } 173 | 174 | for y := range input { 175 | ch.StartProducing() 176 | x, ok := <-products 177 | if !ok { 178 | log.Panicf("Expecting more products") 179 | } 180 | tmp.Mul(x, x) 181 | x.Rem(y, tmp) 182 | output <- x 183 | 184 | x, ok = <-products 185 | if ok { 186 | tmp.Mul(x, x) 187 | x.Rem(y, tmp) 188 | output <- x 189 | } 190 | y.Clear() 191 | } 192 | } 193 | 194 | // For each input modulus 'x' and remainderTree parent 'y', compute z = (y%(x*x))/x; gcd(z, x) 195 | func lowmemRemainderTreeFinal(input, moduli chan *gmp.Int, output chan<- Collision) { 196 | defer close(output) 197 | tmp := new(gmp.Int) 198 | 199 | for y := range input { 200 | for i := 0; i < 2; i++ { 201 | modulus, ok := <-moduli 202 | if !ok { 203 | log.Print("Odd number of moduli? (should only see this once)") 204 | continue 205 | } 206 | tmp.Mul(modulus, modulus) 207 | tmp.Rem(y, tmp) 208 | tmp.Quo(tmp, modulus) 209 | if tmp.GCD(nil, nil, tmp, modulus).BitLen() != 1 { 210 | q := new(gmp.Int).Quo(modulus, tmp) 211 | output <- Collision{ 212 | Modulus: modulus, 213 | P: tmp, 214 | Q: q, 215 | } 216 | tmp = new(gmp.Int) 217 | } 218 | } 219 | y.Clear() 220 | } 221 | } 222 | 223 | // Implementation of D.J. Bernstein's "How to find smooth parts of integers" 224 | // http://cr.yp.to/papers.html#smoothparts 225 | func LowMemSmoothPartsGCD(moduli chan *gmp.Int, output chan<- Collision) { 226 | go lowmemProductTreeLevel(moduli, nil, output) 227 | } 228 | -------------------------------------------------------------------------------- /batchgcd/output-sorted.txt: -------------------------------------------------------------------------------- 1 | COLLISION: N=ae3a9606fbbc40d9354d3cf69499bf9990be935be0170cec81578806d21c7b8300d6b040559c4504379e526448bb71dfb8994388004dfbbfc7938be8eee9a5ea0ce5455a95775a32764f36a83b29832325a94bd1b8cc230a6d62e34c22652d5699a670190a47ef8b3e6ec423e33bdf8baba6ebe7c8118adf581d052414bdb68d P=c08d273990570234ae663e38dd3be5800ba7272d5f1f6c4aa0317cf494b7be1c964a7a272e28c84bf0d4427f5fa08292a543cb490120ef0310cc64bc9ad3e7b7 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 2 | COLLISION: N=ae8188154fc13f2bc500161640fef649087c70fd30c669c8d9e89df85076da92812b4b6c2c482c844c8989957450eb8cfcf492cd76d189b78f364ad40431b042b342b2c4e622032951a9a482b9ad1e039aad2ff2dc0f78d403094209a48d4dfef9a8f35a8660ee84c18f49db150869a6abdbf733cc8fbd75987a18ba388dc21f P=c0db8f447909b148b6cdcd976c6c76b6f268c4acc691a3e16c490e4725aa2566ede869adffcd5b45f0cd162df041a34e864b4ae0b24541fde7bdbf541dd2580d Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 3 | COLLISION: N=af0cf53b086643924c2606f4fb372927915342857130589b6da62fc423d0b57d5770dd77bf49c5f175f69f632dd0878cb39faff5bcbda1e734a46cc583bd390df4d71dba3bd9cb36d01bad770af1c894f88e8fed59812e0e8d378ac3d937c66178351f85fc60714b5718b8de4dbcb54899c4af6de9b396dd7e05b34fad944671 P=c175a6089b422d7daa1534f5df34e8e7798d1b23b51410bd2bd877d52c9cdfd99d1c43be04ad635e84be152d06c9b72112504d322df178215b1780a839abeea3 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 4 | COLLISION: N=af1344d18675c8b77738982da49a202dd1e7bd3ced38065acf1eb8d435e8597465356b8b06fc65f6402ce6d043e04a42b6f784f5961e81eb1717b3f7ae8390cb8b34534e65c0f32a6601f2253bd9f7d0a16a13c9e54c13baae6b7b617ac880633c48ad0dcd9e68031ed0f1f4407cd581d720726ae9d19ac1c4905e02df010c99 P=c17c9f85c0682d75b12791f249185669f71dcdd76fda9fa5e51ac14199d3b121e63ef79ebd06734197254cb6cd45bacb70246ce38ea613ddbc3908fb5f87ed9b Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 5 | COLLISION: N=b1a4e801acc848d60e4a6a501c4c93e2b047153fe88b4c4686220920107e799074da02a6c094847ce7cb388ef9225156049a620c5f1c3edf9e0cc7fb2693cbf9dbcb1c7363c7ec70e231180c3a669fcf15edd19f74cef2725a58ed753c1bdae9edb6d6bbdc73c6a51eaf6bc35592f00b57dae2acc312c038d6eec8c16f474ce1 P=c4536b8b0c1272693a59ee82fbc38fd688d08f7479b6383da2b6bf52c931a68426db9d0df1780f038efbfa9c450f67dfbb688b233d7493e326cfc696072984f3 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 6 | COLLISION: N=b47ae4e57d5e691a33fe8d8799af1da5bec33904b284fde7476fe245f608f38f176b9fcaa5406ffbb797e680892defc3ce403a06164b1c1f549f31bc2fd887fbfcd1d570d561f03568770ca318b3ac865cd725a451886311e49511f2a6e25e4a5bb8388a239dd894f323d9617279683635d4a0ee115de0538b03dcef3a7e5367 P=c775c162ee0b5119ce6c585d761acc3bf8618bc2560d424946617f3fb6710c9ae31d62d5d08ab864c00122e1f856dbac1b3873b614857c864d258b0ec5c43265 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 7 | COLLISION: N=b5ea3055af7ac1cd2bf3a22b24ee173bcc647a88410803849b2be7a51473e309135ab5b45b64f0536b0c008aabee53a4e3f4ca658f29da4e1887d3e82e5ed00d390e50d7c8957394f10c30e8f1e50441d09fba672e388eef07fd843ab236aee7fb4bca384880fbc3e1c8d6a0a0e5bdfcc19faba86b1c446b2310b22c1d1366f9 P=caadf9a149ef6bf4a6f8a2a3515e9bbc818c1f4ce2c2587a5b08cae6b87aa08090ab4c8c93d795be8312b7eece91c7c9fd39b8289b9ee6190042467d3097bfd9 Q=e5c5c02e844bfdb7598a2f43b11157ec3f0b55da22770cc6df48588327d2eabac36b4fb7598df79ce7c4923bbd71941f4d975eba907c14d4390ea3f5cac18c21 8 | COLLISION: N=b6a9c5f30d4f820bf628a006a705162dfe7735ffcf4b52aa4b3e0e770d1f3235a27aff2ce1e832603fba72dee59ee8ce0b81f0c1385fff016373009210bfa5a68c22a7df7a3a66e1f3d621b593452009d20762049da811c30391358944dfb0a4bb3daabacf8154955b4bac28008fc7ab414b70857593132249e68d2e18ac8727 P=c9df68832f381be52cea31659021dd7c7228aa50e1c27a41f801a2a7b2ad9d66c834240bfdced48789a7358e815bbae05f4bf5c8a1c6e2ebcbb77c5aebf8f9a5 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 9 | COLLISION: N=b732615b6b9869074e504d1890d265a6364d61b5a4d8b7026feb1699a35b3cd72b2d1fc7be7c32455bd7ec98d052651d4081274a7f3cf83ec034fa39eb2b1e0b87890188a9d642cdfd1da22077046d5462a31ae7894fc3bd12505fb5300ce7cfee63be613d5cda674928ff3fac466e05d3237eade86ad757e4c204bdbe40ce7f P=ca7661a3618b55f365309f1c15bbf9eb6bff3950932eda676c1fa3465421f71ed36605bc041c6093e2ab36052b7f577a47bc8cc4c9f0d7f78e88c0821c785b2d Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 10 | COLLISION: N=b87126596b5253e1482e599c5ce5c0b0cf43a0aba4e5e0727373da6f80b4d110adcb6f80c2059f679f314b0cf97a751c43c5d0e94380c054c4918a45b5bfcb96556b8aeae243daa5bbdadc5e6fff23c4024ed72ebc0f33d043bea4c105d0e653a92ceeb13f0fcbc0024b5ba1a9c699b6c955375c1cdc756bb8324f3419e7014d P=cbd6ac80b0a586e4ee54e3e5a2bd7e73a8718a6b37382e7cc4cc00c76b349be4df0d51cfb55ff0eb2cf997487f737f349d43eee38fe3b5d522cd486ee41923f7 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 11 | COLLISION: N=b8a00b05b7ea748f58fa56a77c5cbba392fac558544fa4ec0ebfabaaf2916ae966558aa800efd554a645f602e78f81baad318e34b374120d2d3530de1b9d242b9adf036ecb331ec0e9cc3f867d2593e6756a7ee6b467bae1bf18c8de4d6fe475cc7e3c8f050e9065e34deb86ec08e61f4d75e46b096072ab6adc3c753153c0b9 P=cc0a7fa14882e94a9d4e1166ec5aea1f4d49005159ef00ef65dd0591b2da668c69f1d5d8a07753215cebf7ebd14b10c78b2c9aae35c72da632a624fa680b53fb Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 12 | COLLISION: N=b8f27d2ea97e4954351594fec2a6fff21cdd4187517da0e3c07f1ec856bc230e6156c6436a6b23595308d224413953e17f7c9482fa49782cc469ea51426f70880e0cdf2e3a915759d29666b1810a8351b39a79978f6b18e21e858d58660d331c879f3081ac54d522450644676460eec095a4687d907fffc994368e663b79ed51 P=cc659d639a7c0f74cac9567c644ff115fe637eeb98466d072dfdfd740e9041e828bb7cd803c1f3cddfd39783bc6c0ba2bee5788080f731daa03fac84db760943 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 13 | COLLISION: N=b9018fd0d860cbdbb306b1da1c762a64eb0ee6f44cdb33d5c41156045ccc07084b7324602c9674471debfe082a577084766053a7f6e0d710d5c3d8d7fe541938cc8a003f67d4f94151c4324b7957234238764383dba737c4c487a6e87c4fb13446835ed9793677522137a9f9aa2306f5548182e5645efc01f563e43f28098e33 P=cc7645cf6e9165874173fe7ca1990fa72fd26cb5fb7c46c9b2ac733bee24baf0eaf9f24237c5346acd6d457119db7a78e74ed21b58af99a0ff622f319f196289 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 14 | COLLISION: N=b90a4fe8f55a85b36c10b8906767a5db1455b30a1d83297ff422b73d6a293ea6b8e8418e972003198e43c1079472d8153e694027c2935402772ceaed669158c995f1b9dbfae4460267d1635a45314ca7cb80b768cc6a4f4ef63de334537fb8c818bcefad281436f58f9602c3fff92e1c1c5dfba1d11d680eaf7a83df7e978381 P=cc7ff17b141750de2ff603eb4877c50cc515acf771a1a1333d7fdf7d9755080f01e3cd5282c3f234dff9fd75db462d9076e55512f874526718396a5d3a5a3ad3 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 15 | COLLISION: N=b93848a092d3518c7405d4b7abfafe0bc4ef9a133f8b7e88d2d1371ae8e460a6f355ea3bf718fd4fe3d267e30b0b292bcca2eb4af547bd6308feedeb3780b21e158afa94fa9c9d41bc5889eef67290fdafa08a9075a8f5878d0eef4a18fec02e7226f31c78235462822d3214e8926a9576773d2fc6c56c2068d43b3cf1884901 P=ccb2bfd69d8b962b6fb080556d91f051d4dad504a235e019c3de19ccc7b8b3a5274706fd8457d0187ea6c79d3fc22644ca00426406f7dedfe9c88603c4ae4353 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 16 | COLLISION: N=b992d7011b62c664b0527a0c2742231f2aec63624b10031d8df679ec75593ab0a029466d942d32fde907193b6b7a51f22b2f549b438007b2dcd4ae98bad680a06e52af6f34a7978eaec30a14fd77a15f8b8013c9d682de63c080c93529ff601a4d01085765fda935f3cc6036ccf689cbed6349961d3393b177d966345e86e2cd P=cd16d4282e80eb4ca7f946eb7dbcd036ea5b703ae41436306ca05220448e767fa6c328c62b67a40a34c809f1d6d188d6daaa18088d97821c33984e0d8ad34077 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 17 | COLLISION: N=bbbbf20e729818033fb6bf07e097892e4a477d33892d3a754f6b19226192ecbe0062c4b1336017345ac14724ff73d7fdcb296af5c9c51504a3e1d5a6d812a06a6403db5c55b980bca6f7684ea5f7717f9b830c7a7907b95e4fcdc73c1d4865d8df671b95e0950bdca440f2fdd394536b5413260ba2381dcc26897b1e8cbb67ff P=cf7a19d9a9640c8513dfabfba15f001b18747f63691c0bb85aef6b9737deaf0436af827a63cb8ec7f22b460328c1444661907cd46c4964c6bc3cbc1ea25c1fad Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 18 | COLLISION: N=bd41e4d71e5ed4f7dc95989c20b3dc99a5c0bbb10d0c1de9c2657043b6b0ed3ac6e592992aef88443eff7950d9287b8151be3217c22614006c6aa8ffc9f170526933a07278b6a915e48d8fc0e4944eb7fe9144e0583cfa9e30386a021eda67a2c3d6c758eee5ee66ccfcb9b5aa4722a1ace49df6b3fa518badc4a47084f22145 P=d1290ec66bc66f982d67cfc16903c07c206d93094de98ff5c93c2474901749f61001e1ff3866bcd226518aa97758c13dbe4e40b756feb012f4f5866d527fc15f Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 19 | COLLISION: N=beb5ebc3ba3088dee95d49be9a68f817954c998d9e4abff99839874fb049ffd8e692b345a08c8e9aa6770798bbfcb5b423e6f7c90aae9c53261a27a357c190a1d783dd77ec659a8c7d24ac2efbba4b8595e2cb034faeb2b6aaaa8ebbd0a598efea97f7643fbe502af93523d0022eeeef4a68351a8e52db4b3f456b2e579ccbcf P=d2c4355d56fda34b226b075541adc37a4de04aef73c5d83001f6eb156413c37056b91563809a70d56ce5995a5a247208d423f70ad361f6b759966fa64d92fc1d Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 20 | COLLISION: N=c067b4daba7fd684978177c0876b5df217311c77a0dd0326778dfcf5fc54ffa1e4fce628a420ab4f9b53744c7c7b3075bb91837d6b62bc8155446575a6a9af412600c5cf684ca5d20704069ad789f1dbd102e67110bddc431bc4a6c6d36d83aed6d0890e60a338cad153220a6812b6334c1009c9daabe5fd28e9cb6a1489ec41 P=d65e087517e40bf7e164d6b54f9792700356856399c44c95f3f4720cf3946b29ddac3433cf986af946ed64225cbf367b5be48f6ce046da7a0da115e223db5c21 Q=e5c5c02e844bfdb7598a2f43b11157ec3f0b55da22770cc6df48588327d2eabac36b4fb7598df79ce7c4923bbd71941f4d975eba907c14d4390ea3f5cac18c21 21 | COLLISION: N=c07d97b1fb62df44137f6a51ce4151f5252758f686e196f0990a17dc698aabb762248a2852dcebc7a2776e07c6bb9008956dfa1828bb21ab725378328cb5bb30428ea67a280e4c0c36800b6ed268ec4f59a3c6499f10f4363b042fb32f6e19adfd41218b8213eae89c718cded629a8f5eb1d41718b2352e1fdade23d9569d38d P=d4bbccd429ed72125c3e1ce41520da1d2152bbc9fdcc7b9e5a9bef3d5c42ce9a5fa5da2aaf583e69ae1c8a44cdbb27f5bc8c8b43c4863040aad89b57d1d94eb7 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 22 | COLLISION: N=c0fdd645c0bd57d85e6ed7c9dad7ada7100549224d224f7b339a048e88b095a62a4fe7d5a50743c509f1b5ffa7f73b04274b96ba195bf0bc3fc6c17a108bf2659b75263d657c4fc834d4d7572540edec9fd84ce1e4df045307f02fa82860c7a0a74ad41bb99d9cb32c4b40de627cb3922a17980713ef2574aa3468ca68a96815 P=d54987fc9865d69e0e5db60a2583dca1578728ecdf16029213021079e1a736bf7848f688ea0036974226f16063237cef6153b57f0a4b57322ba5db15bbd736cf Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 23 | COLLISION: N=c1562f34ee21178d507fca70410a036ec1b1a671e0b2cf23f01b60135e7282a311c656aab7bd0f083b9aa8657ab2e44f2dec5f58e87a1b5d2de2165463af53c605c65dd0efea21b5ebf63bd2a8514b5ff5e446dfd0ab4ca161ae8dda441eff41712ba8cde0bd712ce3a6ae3d1e322b8fe638507256f1e2186930ffc7658a51b5 P=d5ab2b6606955555cde0fd1a7a97d5785bd14e0f19b77ceda20f6d3745ba9d3b018fc764924daa3dd1d96a67835598f1378b7360a222cac4479031887139f5af Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 24 | COLLISION: N=c21df661d00cda06ea2698ad85af111c9100e692d79d0ef9dd0654200546c3a680c555a01a5cc10b568faeba94c3107eb5700e9b60502fa58fd8c238512ee4c30c5a1a4b4026be141a600ebeaa6e8546349fc3f30d83a3b7b44b61e78dbf455eae01e5fa31de408c4239fe3407543ff14b4875639822c0875dd226d9a930c7cf P=d687f4f9069116ee697bd124d6254cf37c4869c97ef9ea25c7990e4da7d717e7d100e00739c282864a40d537716883514e641bd4a152579a4a1605c9062db01d Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 25 | COLLISION: N=c66e3224fe4fa241e740adeea7a44b205c337baec11e533871ea7ee5b57f7e69c7e7879164f136728c5b2ed4a63932b8d77533d4a56166440e67ff267eb85febe091d7e6e832e53e13a69df566e10ee13d404cca66b28d0f54f5c88faf082f607a5a86d0a807b89ac445dbb8d79b9a208e39f2f9a74e3949872a76fd49e8d1a3 P=db4c50c4d0a570f02e9007e4af255cae5e872352b3ac24564a3072beca0d6ade69d2f195cc8fb45f222c0aff5b9f201bb388ae52ac02ed5722b93471ed26bfd9 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 26 | COLLISION: N=c8012afbcc86b27c44058c491c74a4d424b4a0b1f64fd367bbf917f40677fc217728f9a8f4d383f337f1e910d3a4bf6e2ff44c2b6cffdb2eed937887c8c36313b4d9200a336da200865638a6150aa9d41fd99e97b33c2861581efe428a7735d4fc6725e3d688674c9f43f06611c535ba713f61dda95c710d5c2e0a594eb0ba55 P=dd09aa5eb9115987b5ebff12e6638b1f64db59efa884b9a7030dd8297722fd6bd89c741397508cf843ff7e1b59c7799085e13f904990de99ed2a9429de8ce18f Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 27 | COLLISION: N=c808b07f798e979b0ab932187a70cad50a8fa8a639f5cb6aef0671ad1974a1f03b2545ae795d22b3845451896411da2be893bf910bc22818b8a1d38fac1e43cd0a8a83bc1c3b265abc0a9eeeeb3761e4bbe9105e7f02b40d3fd86e970d1596580c1770790dd56ce500791aa63fc5d6b3ec927dc0e93b20f7528358b285690931 P=dd11fa60da862168e96ed8765fc846f53dc25fa7273387f371949f7d2538c6a9543506f746ab2bd6077dea9c139616bbf213dc0161cb8da803e03b1e6fc912e3 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 28 | COLLISION: N=c8235258a2ad5c179827419e3e29958c6469b545f73d6fafb01d7996481dea5466f368edafd4bbd100adf6cea328c2c09f37df03e956822780f338a7dc8989efdb171f46174927e20ff1ae8327c33db67a625a6eb09d416298e1332062144ea00a9ac2127638f85ba18a600e62f3134869c960322a64d8c5d46d250e8327e5d3 P=dd2f6937441d52c664bf8258413fc1f4b15b6e865b52937ae32642417f2cfa8c9edc6bfb2d042ef866c827fb671ca1e91619e30b748313db26480943afb6cb69 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 29 | COLLISION: N=c9a17d0374ab5193dc18fdf663f0ed425c212cfee67afb0018f7496346666db5ea8b6ecbdf5973d760150bbfdf3cb9d16ed182d8e98c4fed242116940e919e14e927c9d1f581b58f14b98739d71a145ad1dbd519edbed8786b5ecdeca8af7646a30ae3f171226da79f2dcb6c11becd091b9fae0786277304434abf4ea6bad5ff P=ded5c486a9a1f3bb0a722edbb1ad1cbba4a399b57f6f28f914175dd2d829c353cdaa5dcd9ae8c5969008b5334a5308abd311bede68cf5ccad3ae3a690d7cc9ad Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 30 | COLLISION: N=c9d953fdf340a1efed50394d0c5d6ac4a6e8e95b30dbb9d6f657b8f874fda6f8a070d91894c8887e8e952af112dc3cc002e06abbd5c14206ffc105c2c8033b5867a4eb54d77c79002435b5b3d5b113ac19265139b0690f819595157b6f4a3ba4fdb0584b24db5cd936e784a41b3e81805a44839c573063f6a570e881d7db0aa1 P=df137ad0c402db089d99ea7a416d5bf990c4880170f4edcb3a5f51d491763e54575d76c594bc9a7da369b9ac1c16bdc1aefcdad2f205941572a41328d23a0a33 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 31 | COLLISION: N=ca68d15b23107f72a53c546ab73d13253c652a1362549c9e9fc69ad496826968f61c53143da08099892918eb69f56915b1e81ffc287fa80b1bb348b894d6d1199bb972466e30f8cddda94cc6cec8420c09efe269161e105f21e8a084a054fc45cfe040a1dbc8572137053cbb2f80fad1be0134b77730cab438d71da8f2c261f1 P=dfb20f30f25827b74f5d4f51aa5bee40c332792b8c254282ccb0d323a0b41c3c0abd24125351550b1f41196e42d190206ee6938ad2d48e6b692f045b1cf2d923 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 32 | COLLISION: N=cb0d11133cb46c1d49325565eca0d15e3ad05317930919afe26780d750cd643fa6c25ccbb71e50d0629ab710c23355e4fe4de4ae7f4aceed3f90da8bcc34c19cec6142d731ebf146d6c433dc2d7829ce7f8193dd9d0104e17de2275540d714c11758c3f3acde187c182d72c8ef68059e8f1b94ae8680d4bbd26b442258e0bae9 P=e23a7b3cda0635a78735ede2ce7f458d2d42f2a3e937d33c17d59e500b744812e90f9d8ebce46d400000c1e1056063a549d78a2bd42287138b79f8c3210b15c9 Q=e5c5c02e844bfdb7598a2f43b11157ec3f0b55da22770cc6df48588327d2eabac36b4fb7598df79ce7c4923bbd71941f4d975eba907c14d4390ea3f5cac18c21 33 | COLLISION: N=cb1e666a34f10465c84d15a41479881016c74b7be523faba048a7bbf934d15c4333fe76d7b6033edf7af474e9ca4a522e227393e0d2f4850c97494c6a2371673948279af30c9dc755c6ea2c4f58263542515aad7315f20352395d2bb1864d7a6a073e55854374c96b40c536113ce9a64c2ac1355670c32adbdd28dc669be5f2b P=e07abcc8e8f8335a8df34591e2cb0a3d4c26a6f4038425189fd81e54ca2646fde00cfb0fcc689c59a71a8b136a6493b71173e8c844d41efa6a27d78cad9c62f1 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 34 | COLLISION: N=cb7e9fdc010eff031fe2886467fd8010674718b31e65d1845d8db8c95753ae40ba7a5f5ef90b2d0477a51ff936fdd7eefe5709e36db420ac175bbfad60d4a6459287c7cfac4ea9303caf6b638e51c6a24c4c26ba9e4072a5451de925bb58c04e26ba576099285992024e1f52d8faca2830dbdb2f7b552122eeba2b1b48a4b34f P=e0e514c531db3e3c19fe1d2454f0c1cad32c14cf65438241fb9248ddc20e561865e9d69dcf90339a0632fbc686fdc72788a67966c2b07b93a632320781970a9d Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 35 | COLLISION: N=cc29d8cdc4b2cf470b1c7fcdaf0bc028178e49b783a3137afa76ee7fbf3b0d4852d12e174ca2cee535d46f830ee32d5f98bf75020c5402abe324c6348ae685e7b0df9a67c2a38af89f534765a402499d2db70dcf6482175a2f5dda4bbe21fd283e64ae381e265c2980775293a1936ac7d8bd11551aacc11a90370d80e1738097 P=e1a24f57f60594a4c976d65b192b46e8ac4e46238f55f24b2099bd422b67d55fdec5193487eb9065c788f8cffe63ddf66d645604b209a7a035376443ef3658f5 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 36 | COLLISION: N=cc39fe19f3631a4d40392f768e00ee39fe076520a437e6ba12d089d00947e15f784bd97342aff3c73b2d8b97fbeb8937886004c18e727fb3033a2f0e3a47aa631aad575369a617e596bf436f290a66c26e6cd3cdbf0d79443185d1001aafc4c14afea58fca0ac0eeb349001c7b7a31687f11cb89c58ba09dc94e4c23d06b8ef7 P=e1b427504287d0ca71c0d0c7c6b7d6f59383eebcfac14b067cacd6147cb7134a67a4c9ed6fcbe7046b62bb6a8d145342c663e8a7d337282834ea8d48546c0215 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 37 | COLLISION: N=cd975951317f618d70c8dedd634a812613f9489d4d02fec4518d38cc4b64c74e911ee5efb097325a1d8efe4968751f402890f5fe629643489682377d6a5903e9f59ad3ade3536a6a6673998994b76383b227aeefb84b6e41d7b649de49a68ed369249cef1962d861a3744b664e12deee8c1f22adeaa5bfb44af88c098a9c9ef1 P=e3363fdb1af101447ec495a92ca1fb8e2116a303e673959c66b77769bd19b74ede3892260b33711ab826426fe2e38bef6ff53c504e4102e533bd8a1efaeca023 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 38 | COLLISION: N=cdd72e53c041415619de0d78c581690eb23481dd42fd78e97dfbbb4e6b4eb7c1402e2920ad44dd70fb67db8e588ff5722f22ad24eecff3a2882cc588f08a6c1a3d5e97a08867839f7800aba21077eac45876690091379d5aa8cf21729becba2f2096f449a6cbe3dcbe7765b09361a69cb9161a36ac321516afd79e0973f92f4d P=e37ccb5845ffdfd68d408c326766760acfa5a8e1ca0d2304e65c2a5fb207992ca596aef03b0608e44511086059325c78f13e834b318d4ef853adca20b74e0df7 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 39 | COLLISION: N=cebefdbf97beedbed338afdb8ed34d4b42c940b9dadb221f333da0e94e2c355027dd94b4ed9c6911fa8edaaaf893f1f222e2c506379b1780c93ab818164a4b71d4ccf61bd344feb72ea93b7bd6eafd0383c8b4ec9579dd5aee9aab86426dfd3064c812b4bd15ac9f1daf6e4484338b39733093a3f644c5dbaf58dd301843ae39 P=e47cfb88383f79e633094e3a84a46e65a9a22239348c3af551a5123117bb3259d4805828672f5a0a7947d2933a95e8fb6cbbcec61c5bfb1306bf6eccfdd9547b Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 40 | COLLISION: N=d0b51cd8780d5ad8192854aa1a96d5c2c7a5b0896bb17e71603c9be1632849d6b3e8e767072ba4399fc4c4f9f9979182473b391acc70a8b34b662a6267c1290abe614e1cbebcd4b5084afff011d39094e91a14cab19229023fe2f9503ac3d9d5318abc6e8130c49953e397e738fb04f579c3cd0783f06e70872dd2900e8fb3d7 P=e6a7e8ae266e33a833681b6dfddb5a78b0b9232981c655cb3fdb42103cc77c1c804b1a537afb268669ec15868fc12612fd15e2cb177729e39e1f2e70e948f6b5 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 41 | COLLISION: N=d0bb0ca2c809e5d72aedf9d7a53a14cc629a5b9b15583cd1cc04198b17f82b78fcc99dd85e64134c760f65ee65ed33a7c6ffa4330df891c8b5c2258d85ead276a927c51f8ecd46d58f6d832ee507c4c4ca08fa9e29843218bf437489fe542645bbe5982c25bb53e7242704351b6149566cc2073e19248c7dc9ab863aa56bd471 P=e6ae784c109fa8ee108ac2ecde6d3ba88ce0a8293909918867ea588ecf328ba417b6739494ce4bb98eb9e330c6934fc610fc836137a7e1816e1c3dd5a4def8a3 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 42 | COLLISION: N=d1288cb4f557c0310d05f37a6343a9ff062dbc4b19d1459eca56fba4ed173efc686d339a964b8edd47e58cadcc228231de2cbf897d9fda19cbc03de508bc6d5c67ec2fe13e40f35f73ae552989f10816feda98f3f77497869ef6027baacc311efd8a3807acda1e4e638548a6974c586f5b6bffc1189455eae4ca0dd3124af095 P=e7277c521f7876d6220287eb84b47bdebaecb579b7d792c8f43f5aad2663f561bb1191396e5760708097bd1a5664aca4dd6c76cad34264a7c62fb010e742784f Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 43 | COLLISION: N=d14bc685bf8b1d4fdfae66e41e62b813427d260f4dff99780dec88908830dd62e97c174314f0e26afa138be19feb8ff19f5d2eb93fdce7d55115032f976227ad7cc83e5cb8e12b81828b634032c88053c8b627cb0b00dca599460b84e4b7583561c56c212c5876ee6bfe4a7757155d2742f8f958ca0e78b4a0c69dc0e4709937 P=e74e6a7b682bd2e76cad3921f501e223a99d796ea1ebfb8c8ea4f440fcb4a563314d9048af82a1fb2f8bad82a759b86db3c95c10f2016c0932fd774d55a454d5 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 44 | COLLISION: N=d1986b4736198c1990486be74c1d984c1d76b4e458822066bed209e6079768ab474efd2018c76fbe5f798446a14d967fdf560861aeae92e52b4409e7cd56285b5271ac0b2c5082d6c837a3254e24287d1d83c55d8e23d649cea8d9dbae092da519d26fafdf052f9913d84e26d05170d2bbc692a834594eb410a8a83ff78717a3 P=e7a31ea061a5a22057c65c65691639fb8df00a78291355672ce118ccaae71e796d7e6e42cc8e8e25ef84b76aa7e3abea33b6d2ea0a142496dc8fc8dc950771d9 Q=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb 45 | COLLISION: N=d5a7f4738352481f77806d16557f3ac8851865a3fc7908c559a1575cbafcec4164f72f334a97b13c7b3c9bd3ffd14bbba849c5b486a5aa86d0fc197fef8e809e874fd0ee01f1f440be74ba649d8e28df573c6d41e9348aa532d1f2de509e6e0adb5703ba9038bd8c51ee0969d9d8c4af1483cc803839a24ec13aadcb0a2d2103 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ec1ffa0d595b92a6d52eece7deeac962e640fc3dcb42ac8d5950774744784efa51e1b7e7808a47280a84d078e74fa31e2a560385d2b5a648492477a31aad7bf9 46 | COLLISION: N=d5c1b460665dfcde5cafcb2553d1089d1643124c36ad0db95502e6c7b404ae4df0cfddf94cf52cfba6d0f91b70c4b62dbc9e5aae8d92dfa5985f5696361d278e581e809c18914ab7ec6faa1f4a19d89b3c4676101aa33e91dd0c4bff65508a20958d748c4f6f6de0cf98690e2e00c2139f5879408ac70a4e68a4b65c77cb07fb P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ec3c6f3538014b06f1d96b3fd65fe60904770f3b60e898908d5c828f223d7c7cd459eccc4bb1e32582e39f4c5ac24788db657fea260388b73eb3a86e0ed29e61 47 | COLLISION: N=d6b7a49dce1f5ae0d44763695fc282aae46c78f1f4c22bc5235c54fe152dd9ffb49903773762f35d52f928e713d570e29788bb7b217daf1806c2c2972c4f1ce8ec174d0b1996c4d5fad5667d1961b9ad2308a19f4dd7811761bd4f44894455ccecb4d0db318b48ad9e3492c41304f35bb690d5c5324fc568420da85946a8a48d P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ed4c3c92435a50e4e01740213ada26923e99879fb5902740532b7ab4d1f4239cab5f0b2955fd316d9abe67dae369a2bc2ce1703f78c49c25c33f5ad69b4f11b7 48 | COLLISION: N=d741218e0829410eeb7df458a1ad9f4b9a638ade51420fd9a96c4489585a8b180e9a7009c9402c5a2e01a8dc6b443b721c986e86d5aec54f89ff2895cc723505f3bc3df3e57e971e4d90be41626cb4764ad91d533dc52c50db013bcb8670f76ad6abeb8b87492ec17874f164fbafb6abdcf73410db464141cf5ec9fbea77cbbf P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ede42ef206eea13aebd3cef68c90e115b275d51abba0aa4e197a4bc4928589a392357411f9026a9dbd566cbd43011c9b65ab8e5072b85618f7f8cf65b02a76ed 49 | COLLISION: N=d7c5c07c90df3a3592fbcf290ab7fceeb5605a3fea57a97e45d58641d83149aa2fb3db2148940678e78d1f9d2b9343b67d7a5252555cff460da8d0698455547635d4efe375348472ba950f4cb6d93ad00557e50fefbe31f4a0687c6f554a338173f88d08375a51e9a24b03b63f8df37c4a751fd39301b4329fa50bfd1d4d34f5 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ee76c04743f5f358483f8fd5b23473c301359a1caa70b4cd5644d1c8b91e5d99041efe9139b77aa8570059cbe76f338b47960631ffb7a4f46d22e80577d7a36f 50 | COLLISION: N=d7fa951d7fb33edc5fbb23a340ebcf674c697c45acd723c359f233f8b3924e7b27aaaeda24e05c62ded9516c8d38a0378cb8730f1eb1ec9b72ac43879c28c4472e1055f2a7f396ec3d1e6b410c175f8b07bbf48995e00d6a2d1bd366fa11af7b97954dc3a57e21d304bd203ad6076ed852ad6f5f6b5173121f0eefe6c3805867 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=eeb123348ba262679b6a674d5eda7a59d503e11d180a3732f8b0a2ff5f03fdc6af7a38d6c0d8c1d56cbaec5523fe591915f320cc0e285ad600d5d97a2263d165 51 | COLLISION: N=d949d9e59ca9c9dc098bbaac9a4fd479d3828aa16fb53ae08561dfabddc7c322798e6f213ef5e2611e638192f61233b87daf1a1f077b31e0d7261e3307b66d9b2de6811bed380e4c0f010a5f693396c1e19262477f2f0feb842fe876151ce347c5ce60c02ef282a1a98ddea1e750ffdb4d55996e59f571e450e588fcf210cebf P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=f023aa0c4c16d93898b0ba6bd4a01b409795346eda1c04c181d895a42c76ba4eef371e41afc6144486f157f4767092d4e7a9d640ab650a91621aeb10728e6fed 52 | COLLISION: N=da3610f98a954c05e9d2956decd7271c054e3b0a08615e6a435691940495ac49ec3ad25def373dae18259a4a97e4ecedb3ca0dc1a42293d4660669917e89384b4afc4f09e2976d382f8f494a8014f4443817e6aef13b3af789a99a210ad89498477277712ba564900e0f1813e9720e9c1b68780f573697f676fe6f3e7d1646bb P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=f128b87af5afa22adb5bc5f7e3f4a92d579a4f1516712a41a219bdf47c9ef49600ebcce36be496dc35d09c13dbb8edb167c2b88acda4893e39ffa2e4c3f9f6a1 53 | COLLISION: N=df1620f801a3d45b5b362394a5105631e1afe6b57ddf3e33775c4e5654894b9093ec5129aca1a0e1db4e21e6e365ca4db42fcb7fde579dc10a9c768074c4ae3460d2bf063c7716444fbad05441a52c3b60eece3b176e57fcf4788b108dd18bbf604c6e8b2d74ddf57fc3bcfdd2e0d2f61f39fac61c6334ccfee63a778b401015 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=f68c08a7aa57957acefc6d15dfb2f679511f531173c8896c8e606c2ebcebd0221d2ac6f64ee97f7509fb6d15b0f9d2bf5eb4e919f692c1aea3be9b3e566faecf 54 | COLLISION: N=e35c5d8dd58e2b4dec13712446a27a7f0a4c0e242e104366adef28646609251469505396506b19c58d5e36d52b86b49af59f733379a0ef76d0a2d8d88b95b1042d2bcbb6704b1bf44089ea593cc6f6132e33325374d6c911366b9d0c64bc0f57ef1d4c21a0e449400b7f8ca6dd4ada844516c89871fa17b0914c5710638b5b35 P=e5c5c02e844bfdb7598a2f43b11157ec3f0b55da22770cc6df48588327d2eabac36b4fb7598df79ce7c4923bbd71941f4d975eba907c14d4390ea3f5cac18c21 Q=fd502493105f3a0d185b1292a88dc87436960a2cccff7a2da79fd72705b044f7af91d86724066394c02c30cdf5cbc9a30aa586dd266955b1163b035329eb4c95 55 | COLLISION: N=e46a58db353ea6633f1c879d91befcfae1d20dea7405acde00783102cda337468e12261eb66fdb844435c290753337c281f4e56cd68e4391fdc4461cd4cff23493e0eadbe1d276619e355588da2a08a126ccc9f4c0f6898fdd692b6dff47d4ecff6e4cc287b9fcf7b6d114f6f53b6a48d795b652f8371536e63253656d907ea7 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=fc6fb7db15458fbc67321ac5989cb4288d81be05a8801140f524d34fed0bf4dad7ffb8917b1e1a42d3b36aa2108f5d266d9f33b71cc2ba70b2f8920f89d63825 56 | COLLISION: N=e4ca8991b458e6050a992715217d9c407be6c638df367e037f35b065f6349ddce3d93f948d09938230c7a97bf0223f7aaa57dc437423bdbb6617fa5dffa71038626af3936d30f4c674a9ed311f85f326655a986ce2cc1378dfeb9666c4d80b84401c248eedd365dea812e1cb990ab6e6566ba9606c4a9696eb16528ca60d1023 P=e5c5c02e844bfdb7598a2f43b11157ec3f0b55da22770cc6df48588327d2eabac36b4fb7598df79ce7c4923bbd71941f4d975eba907c14d4390ea3f5cac18c21 Q=fee81c9bb5cd62a89082cd3f7b8efc69c52852453557bec311bbbac103189066dbe677c6073790ecde7188feb540081a835fca17ff0c9dac501c8325091cf3c3 57 | COLLISION: N=e674d7f01fc6ff9eba3946f7f94d078d7c442a8d75df863be97431269509c8649bc93056d43b1b0d79f4063faa100d2c1f97051d3fa7d5dde53c18749232739cc1d78aa35ddd14f1576672ea312e8ddde27d3870d09f559e9c8287d84c2d55107aa0efcb866bf3075db27c12a950565509e309cf5f4a10502bfb5b12aa1ca315 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=feb12985219d3dd073ef9680b6cd07ebf251ca75c9b57a190d0ab2179e03390dee4f5f3893d87c5b624ddf603d567c61ee6347b6c4592841832cdef6213b57cf 58 | COLLISION: N=e740efed57ce6f5d39a75be8a546f717a3769daaedcb9909ddb6eb6e955a0a6de318abcd5c2a5b042d68da33c01ae37aa8ce6bd4c6a493ae8a114cab2560d719713b21dd115afb60b67539d8f4e2084bebf63da0a60a4f42ef9241e8df474f5a3d9261e00c675666ea228ea87e07115ba377519a5b18d21229850ce4d31c7d61 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ff92b81820340353ea12dfb092b252876516e1f9fbc7ee131446d35e1f6ad797b55b991386369cfa9f4e970ea611b04a1351177d84c1c697159bed952d143e73 59 | COLLISION: N=e749f3a6b9a18c0b5861dc549c3c03dd6294091eb2131c4be806d7f617fc1ac56cf8af21cfcf559212fbbd698ef6276e295b9ce99af19178432f7a911e551fad97b656e714f9dfa0315c1e8b9a18c99ecc74348422d0fedec2be394ba0a53a9ef5dd263c0172f1c15016d883a745591fb8e5485d780edcd2098a30f3b187a2e9 P=e7a3d1bde426c7bc9908993495a217a2aa37ef6b42e475f4713cb1050266be578b06745dd7f85060c69afdb2e6e158e8dd68aa187880207f0c95ea2f3b4b6bdb Q=ff9cae81c4380a40f5bcf3cead8f50718fc542c0507eaa2a2614d15a8e37f853881878e983ee6d4a286b3ea4b9e35a7df70287959e51ad8c98e3188a1a4a298b 60 | --------------------------------------------------------------------------------