package ringct


//import "fmt"

const ATOMS = 64 // 64 bit in the amount field

type bits64 [ATOMS]bool


    // implementation of d2b from rctTypes.cpp
    // lays out the number from lowest bit at pos 0 and highest at bit 63
func d2b_uint64_to_bits(amount uint64)(bits64){
        var bits bits64
        for i := 0; amount != 0; i++ {
            if (amount&1) == 1 {
                bits[i] = true
            }
            amount = amount >> 1
        }
        return bits
    }
    

    //ProveRange and VerifyRange
    //ProveRange gives C, and mask such that \sumCi = C
    //   c.f. http://eprint.iacr.org/2015/1098 section 5.1
    //   and Ci is a commitment to either 0 or 2^i, i=0,...,63
    //   thus this proves that "amount" is in [0, 2^64]
    //   mask is a such that C = aG + bH, and b = amount
    //VerifyRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i
    // this function proves a range using Pedersen  commitment and borromean signatures
    // implemented in cryptonote rctSigs.cpp
func ProveRange (C *Key, mask *Key, amount uint64) ( *RangeSig){
    Sc_0(mask)
    copy(C[:], (*identity())[:]) // set C to identity

    
    var ai Key64
    var Cih Key64
    var sig RangeSig
    
    bits := d2b_uint64_to_bits(amount)
    //fmt.Printf("bits %+v\n", bits)
    
    for i := 0; i < ATOMS;i++{
        ai[i] = *(RandomScalar()) // grab a random key
       // Sc_0(&ai[i]); // make random key zero  for tesing puprpose // BUG if line is uncommented
        ScReduce32(&ai[i])   // reduce it
       // fmt.Printf("ai[%2d] %x\n",i, ai[i])
        
        sig.ci[i] = ScalarmultBase(ai[i])
       // fmt.Printf("ci[%2d] %x\n",i, sig.ci[i])
        if bits[i] {
            AddKeys(&sig.ci[i],&sig.ci[i],&H2[i])
        }
        
        SubKeys(&Cih[i],&sig.ci[i],&H2[i])
        ScAdd(mask,mask,&ai[i])
        AddKeys(C,C,&sig.ci[i])
    }
    
    //fmt.Print("C   %x\n", *C)
    
    // TODO caculate Borromean signature here
    sig.asig = GenerateBorromean(ai, sig.ci, Cih, bits);
    
    
    return &sig
}

func VerifyRange(c *Key, as RangeSig) bool {
	var CiH Key64
	tmp := identity()
	for i := 0; i < 64; i++ {
		SubKeys(&CiH[i], &as.ci[i], &H2[i])
		AddKeys(tmp, tmp, &as.ci[i])
	}
	
//	fmt.Printf("C   %x\n", *c)
//        fmt.Printf("tmp %x\n", *tmp)
	if *c != *tmp {
		return false
	}
	//return true
	return VerifyBorromean(&as.asig, &as.ci, &CiH)
}



//Borromean (c.f. gmax/andytoshi's paper)
func GenerateBorromean(x Key64, P1 Key64, P2 Key64, indices bits64) (BoroSig){
  var bb BoroSig   
  var alpha Key64
  var L [2]Key64
  var c Key
  
  var data_bytes []byte
  
  
  for ii := 0; ii < ATOMS;ii++{
      var naught,prime int
      if indices[ii]{
          naught = 1
      }else{
          naught = 0
      }
      prime = (naught+1)%2   // basically it is the inverse of naught
      
      alpha[ii] = skGen() // generate a new random scalar
      L[naught][ii] = ScalarmultBase(alpha[ii])
      
      if naught == 0 {
            bb.s1[ii] = skGen()
            c = *(HashToScalar(L[naught][ii][:]))
            AddKeys2(&L[prime][ii], &bb.s1[ii], &c, &P2[ii])
      }
     // original cryptonote does NOT clear out some unset bytes, verify whether it may be a problem for them
      data_bytes = append(data_bytes, L[1][ii][:]...) 
  }
       // take the hash of the L1 keys all 64 of them
       // we have been collecting them above
       bb.ee = *(HashToScalar(data_bytes));
       
  //     fmt.Printf("bb.ee   %x\n", bb.ee)
       
     var  LL, cc Key
     for jj := 0 ; jj < ATOMS;jj++{
         if indices[jj] == false {
             ScMulSub(&bb.s0[jj], &x[jj], &bb.ee, &alpha[jj])
         }else{
             bb.s0[jj] = skGen()
              AddKeys2(&LL, &bb.s0[jj], &bb.ee, &P1[jj])
              cc = *(HashToScalar(LL[:]))
              ScMulSub(&bb.s1[jj], &x[jj], &cc, &alpha[jj])
         }
     }
      
    return bb
}




// Verify the Borromean sig
func VerifyBorromean(b *BoroSig, p1, p2 *Key64) bool {
	var data []byte
	tmp, tmp2 := new(Key), new(Key)
	for i := 0; i < 64; i++ {
		AddKeys2(tmp, &b.s0[i], &b.ee, &p1[i])
		tmp3 := HashToScalar(tmp[:])
		AddKeys2(tmp2, &b.s1[i], tmp3, &p2[i])
		data = append(data, tmp2[:]...)
	}
	computed := HashToScalar(data)
        
//        fmt.Printf("comp    %x\n", computed)
	return *computed == b.ee
}
