236 lines
7.0 KiB
Go
236 lines
7.0 KiB
Go
|
// Copyright 2017-2021 DERO Project. All rights reserved.
|
||
|
// Use of this source code in any form is governed by RESEARCH license.
|
||
|
// license can be found in the LICENSE file.
|
||
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
||
|
//
|
||
|
//
|
||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
package walletapi
|
||
|
|
||
|
import "runtime"
|
||
|
import "fmt"
|
||
|
import "sort"
|
||
|
import "math/big"
|
||
|
import "encoding/binary"
|
||
|
|
||
|
//import "github.com/mattn/go-isatty"
|
||
|
//import "github.com/cheggaaa/pb/v3"
|
||
|
|
||
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
||
|
import "github.com/deroproject/derohe/cryptography/bn256"
|
||
|
|
||
|
// this file implements balance decoder whih has to be bruteforced
|
||
|
// balance is a 64 bit field and total effort is 2^64
|
||
|
// but in reality, the balances are well distributed with the expectation that no one will ever be able to collect over 2 ^ 40
|
||
|
// However, 2^40 is solvable in less than 1 sec on a single core, with around 8 MB RAM, see below
|
||
|
// these tables are sharable between wallets
|
||
|
|
||
|
//type PreComputeTable [16*1024*1024]uint64 // each table is 128 MB in size
|
||
|
|
||
|
// table size cannot be more than 1<<24
|
||
|
const TABLE_SIZE = 1 << 19
|
||
|
|
||
|
type PreComputeTable []uint64 // each table is 2^TABLE_SIZE * 8 bytes in size
|
||
|
// 2^15 * 8 = 32768 *8 = 256 Kib
|
||
|
// 2^16 * 8 = 512 Kib
|
||
|
// 2^17 * 8 = 1 Mib
|
||
|
// 2^18 * 8 = 2 Mib
|
||
|
// 2^19 * 8 = 4 Mib
|
||
|
// 2^20 * 8 = 8 Mib
|
||
|
|
||
|
type LookupTable []PreComputeTable // default is 1 table, if someone who owns 2^48 coins or more needs more speed, it is possible
|
||
|
|
||
|
// IntSlice attaches the methods of Interface to []int, sorting in increasing order.
|
||
|
|
||
|
//type UintSlice []uint64
|
||
|
|
||
|
func (p PreComputeTable) Len() int { return len(p) }
|
||
|
func (p PreComputeTable) Less(i, j int) bool { return p[i] < p[j] }
|
||
|
func (p PreComputeTable) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||
|
|
||
|
// with some more smartness table can be condensed more to contain 16.3% more entries within the same size
|
||
|
func Initialize_LookupTable(count int, table_size int) *LookupTable {
|
||
|
t := make([]PreComputeTable, count, count)
|
||
|
|
||
|
if table_size&0xff != 0 {
|
||
|
panic("table size must be multiple of 256")
|
||
|
}
|
||
|
|
||
|
//terminal := isatty.IsTerminal(os.Stdout.Fd())
|
||
|
|
||
|
var acc bn256.G1 // avoid allocations every loop
|
||
|
acc.ScalarMult(crypto.G, new(big.Int).SetUint64(0))
|
||
|
|
||
|
small_table := make([]*bn256.G1, 256, 256)
|
||
|
for k := range small_table {
|
||
|
small_table[k] = new(bn256.G1)
|
||
|
}
|
||
|
|
||
|
var compressed [33]byte
|
||
|
|
||
|
for i := range t {
|
||
|
t[i] = make([]uint64, table_size, table_size)
|
||
|
|
||
|
//bar := pb.New(table_size)
|
||
|
//if terminal {
|
||
|
// bar.Start()
|
||
|
//}
|
||
|
for j := 0; j < table_size; j += 256 {
|
||
|
|
||
|
for k := range small_table {
|
||
|
small_table[k].Set(&acc)
|
||
|
acc.Add(small_table[k], crypto.G)
|
||
|
}
|
||
|
(bn256.G1Array(small_table)).MakeAffine() // precompute everything ASAP
|
||
|
|
||
|
//if terminal {
|
||
|
// bar.Add(256)
|
||
|
//}
|
||
|
|
||
|
for k := range small_table {
|
||
|
// convert acc to compressed point and extract last 5 bytes
|
||
|
//compressed := small_table[k].EncodeCompressed()
|
||
|
small_table[k].EncodeCompressedToBuf(compressed[:])
|
||
|
|
||
|
// replace last bytes by j in coded form
|
||
|
compressed[32] = byte(uint64(j+k) & 0xff)
|
||
|
compressed[31] = byte((uint64(j+k) >> 8) & 0xff)
|
||
|
compressed[30] = byte((uint64(j+k) >> 16) & 0xff)
|
||
|
|
||
|
(t)[i][j+k] = binary.BigEndian.Uint64(compressed[25:])
|
||
|
}
|
||
|
|
||
|
// if j < 300 {
|
||
|
// fmt.Printf("%d[%d]th entry %x\n",i,j, (t)[i][j])
|
||
|
// }
|
||
|
|
||
|
if j%1000 == 0 && runtime.GOOS == "js" {
|
||
|
fmt.Printf("completed %f (j %d)\n", float32(j)*100/float32(len((t)[i])), j)
|
||
|
runtime.Gosched() // gives others opportunity to run
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//fmt.Printf("sorting start\n")
|
||
|
sort.Sort(t[i])
|
||
|
//fmt.Printf("sortingcomplete\n")
|
||
|
//bar.Finish()
|
||
|
}
|
||
|
//fmt.Printf("lookuptable complete\n")
|
||
|
t1 := LookupTable(t)
|
||
|
|
||
|
Balance_lookup_table = &t1
|
||
|
|
||
|
return &t1
|
||
|
}
|
||
|
|
||
|
// convert point to balance
|
||
|
func (t *LookupTable) Lookup(p *bn256.G1, previous_balance uint64) (balance uint64) {
|
||
|
|
||
|
// now this big part must be searched in the precomputation lookup table
|
||
|
|
||
|
//fmt.Printf("decoding balance now\n",)
|
||
|
var acc bn256.G1
|
||
|
|
||
|
// wait till precompute table is ready
|
||
|
// check if previous balance is still sane though it may have mutated
|
||
|
|
||
|
if len(*Balance_lookup_table) < 1 {
|
||
|
panic("precompute table is not ready")
|
||
|
}
|
||
|
|
||
|
acc.ScalarMult(crypto.G, new(big.Int).SetUint64(previous_balance))
|
||
|
if acc.String() == p.String() {
|
||
|
return previous_balance
|
||
|
|
||
|
}
|
||
|
|
||
|
work_per_loop := new(bn256.G1)
|
||
|
|
||
|
balance_part := uint64(0)
|
||
|
|
||
|
balance_per_loop := uint64(len((*t)[0]) * len(*t))
|
||
|
|
||
|
_ = balance_per_loop
|
||
|
|
||
|
pcopy := new(bn256.G1).Set(p)
|
||
|
|
||
|
work_per_loop.ScalarMult(crypto.G, new(big.Int).SetUint64(balance_per_loop))
|
||
|
work_per_loop = new(bn256.G1).Neg(work_per_loop)
|
||
|
|
||
|
loop_counter := 0
|
||
|
|
||
|
// fmt.Printf("jumping into loop %d\n", loop_counter)
|
||
|
for { // it is an infinite loop
|
||
|
|
||
|
// fmt.Printf("loop counter %d balance %d\n", loop_counter, balance)
|
||
|
|
||
|
if loop_counter != 0 {
|
||
|
pcopy = new(bn256.G1).Add(pcopy, work_per_loop)
|
||
|
}
|
||
|
loop_counter++
|
||
|
|
||
|
//if loop_counter >= 10 {
|
||
|
// break;
|
||
|
// }
|
||
|
|
||
|
compressed := pcopy.EncodeCompressed()
|
||
|
|
||
|
compressed[32] = 0
|
||
|
compressed[31] = 0
|
||
|
compressed[30] = 0
|
||
|
|
||
|
big_part := binary.BigEndian.Uint64(compressed[25:])
|
||
|
|
||
|
for i := range *t {
|
||
|
index := sort.Search(len((*t)[i]), func(j int) bool { return ((*t)[i][j] & 0xffffffffff000000) >= big_part })
|
||
|
check_again:
|
||
|
if index < len((*t)[i]) && ((*t)[i][index]&0xffffffffff000000) == big_part {
|
||
|
|
||
|
balance_part = ((*t)[i][index]) & 0xffffff
|
||
|
acc.ScalarMult(crypto.G, new(big.Int).SetUint64(balance+balance_part))
|
||
|
|
||
|
if acc.String() == p.String() { // since we may have part collisions, make sure full point is checked
|
||
|
|
||
|
balance += balance_part
|
||
|
// fmt.Printf("balance found %d part(%d) index %d big part %x\n",balance,balance_part, index, big_part );
|
||
|
|
||
|
return balance
|
||
|
|
||
|
}
|
||
|
|
||
|
// we have failed since it was partial collision, make sure that we can try if possible, another collision
|
||
|
// this code can be removed if no duplications exist in the first 5 bytes, but a probablity exists for the same
|
||
|
index++
|
||
|
goto check_again
|
||
|
|
||
|
} else {
|
||
|
// x is not present in data,
|
||
|
// but i is the index where it would be inserted.
|
||
|
|
||
|
balance += uint64(len((*t)[i]))
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// from the point we must decrease balance per loop
|
||
|
|
||
|
}
|
||
|
|
||
|
//panic(fmt.Sprintf("balance not yet found, work done %x", balance))
|
||
|
//return balance
|
||
|
}
|
||
|
|
||
|
// this should be tuned by anyone using this package
|
||
|
func init() {
|
||
|
Initialize_LookupTable(1, 1<<16)
|
||
|
}
|