You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

686 lines
17 KiB

// Copyright 2017-2018 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 transaction
import "fmt"
import "bytes"
import "encoding/binary"
import "github.com/romana/rlog"
import "github.com/deroproject/derosuite/crypto"
import "github.com/deroproject/derosuite/crypto/ringct"
const TXIN_GEN = byte(0xff)
const TXIN_TO_SCRIPT = byte(0)
const TXIN_TO_SCRIPTHASH = byte(1)
const TXIN_TO_KEY = byte(2)
const TXOUT_TO_SCRIPT = byte(0)
const TXOUT_TO_SCRIPTHASH = byte(1)
const TXOUT_TO_KEY = byte(2)
var TX_IN_NAME = map[byte]string{
TXIN_GEN: "Coinbase",
TXIN_TO_SCRIPT: "To Script",
TXIN_TO_SCRIPTHASH: "To Script hash",
TXIN_TO_KEY: "To key",
}
const TRANSACTION = byte(0xcc)
const BLOCK = byte(0xbb)
/*
VARIANT_TAG(binary_archive, cryptonote::txin_to_script, 0x0);
VARIANT_TAG(binary_archive, cryptonote::txin_to_scripthash, 0x1);
VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0);
VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1);
VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc);
VARIANT_TAG(binary_archive, cryptonote::block, 0xbb);
*/
/* outputs */
type Txout_to_script struct {
// std::vector<crypto::public_key> keys;
// std::vector<uint8_t> script;
Keys [][32]byte
Script []byte
/* BEGIN_SERIALIZE_OBJECT()
FIELD(keys)
FIELD(script)
END_SERIALIZE()
*/
}
type Txout_to_scripthash struct {
//crypto::hash hash;
Hash [32]byte
}
type Txout_to_key struct {
Key crypto.Key
// Mask [32]byte `json:"-"`
/*txout_to_key() { }
txout_to_key(const crypto::public_key &_key) : key(_key) { }
crypto::public_key key;*/
}
// there can be only 4 types if inputs
// used by miner
type Txin_gen struct {
Height uint64 // stored as varint
}
type Txin_to_script struct {
Prev [32]byte
Prevout uint64
Sigset []byte
/* BEGIN_SERIALIZE_OBJECT()
FIELD(prev)
VARINT_FIELD(prevout)
FIELD(sigset)
END_SERIALIZE()
*/
}
type Txin_to_scripthash struct {
Prev [32]byte
Prevout uint64
Script Txout_to_script
Sigset []byte
/* BEGIN_SERIALIZE_OBJECT()
FIELD(prev)
VARINT_FIELD(prevout)
FIELD(script)
FIELD(sigset)
END_SERIALIZE()
*/
}
type Txin_to_key struct {
Amount uint64
Key_offsets []uint64 // this is encoded as a varint for length and then all offsets are stored as varint
//crypto::key_image k_image; // double spending protection
K_image crypto.Hash `json:"k_image"` // key image
/* BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(amount)
FIELD(key_offsets)
FIELD(k_image)
END_SERIALIZE()
*/
}
type Txin_v interface{} // it can only be txin_gen, txin_to_script, txin_to_scripthash, txin_to_key
type Tx_out struct {
Amount uint64
Target interface{} // txout_target_v ;, it can only be txout_to_script, txout_to_scripthash, txout_to_key
/* BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(amount)
FIELD(target)
END_SERIALIZE()
*/
}
// the core transaction
type Transaction_Prefix struct {
Version uint64 `json:"version"`
Unlock_Time uint64 `json:"unlock_time"` // used to lock first output
Vin []Txin_v
Vout []Tx_out
Extra []byte
Extra_map map[EXTRA_TAG]interface{} `json:"-"` // all information parsed from extra is placed here
PaymentID_map map[EXTRA_TAG]interface{} `json:"-"` // payments id parsed or set are placed her
ExtraType byte `json:"-"` // NOT used, candidate for deletion
}
type Transaction struct {
Transaction_Prefix
// same as Transaction_Prefix
// Signature not sure of what form
Signature []Signature_v1 `json:"-"` // old format, the array size is always equal to vin length,
//Signature_RCT RCT_Signature // version 2
RctSignature *ringct.RctSig
Expanded bool `json:"-"`
}
func (tx *Transaction) GetHash() (result crypto.Hash) {
switch tx.Version {
case 1:
result = crypto.Hash(crypto.Keccak256(tx.SerializeHeader()))
case 2:
// version 2 requires first computing 3 separate hashes
// prefix, rctBase and rctPrunable
// and then hashing the hashes together to get the final hash
prefixHash := tx.GetPrefixHash()
rctBaseHash := tx.RctSignature.BaseHash()
rctPrunableHash := tx.RctSignature.PrunableHash()
result = crypto.Hash(crypto.Keccak256(prefixHash[:], rctBaseHash[:], rctPrunableHash[:]))
default:
panic("Transaction version cannot be zero")
}
return
}
func (tx *Transaction) GetPrefixHash() (result crypto.Hash) {
result = crypto.Keccak256(tx.SerializeHeader())
return result
}
// returns whether the tx is coinbase
func (tx *Transaction) IsCoinbase() (result bool) {
switch tx.Vin[0].(type) {
case Txin_gen:
return true
default:
return false
}
}
func (tx *Transaction) DeserializeHeader(buf []byte) (err error) {
Key_offset_count := uint64(0) // used to calculate expected signatures in v1
Mixin := -1
tx.Clear() // clear existing
//Mixin_count := 0 // for signature purpose
done := 0
tx.Version, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Version in Transaction\n")
}
rlog.Tracef(10, "transaction version %d\n", tx.Version)
buf = buf[done:]
tx.Unlock_Time, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Unlock_Time in Transaction\n")
}
buf = buf[done:]
// parse vin length
vin_length, done := binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Vin length in Transaction\n")
}
buf = buf[done:]
if vin_length == 0 {
return fmt.Errorf("Vin input cannot be zero in Transaction\n")
}
coinbase_done := false
rlog.Tracef(10, "vin length %d\n", vin_length)
for i := uint64(0); i < vin_length; i++ {
vin_type := buf[0]
buf = buf[1:] // consume 1 more byte
rlog.Tracef(10, "Processing i %d vin_type %s hex %x\n", i, TX_IN_NAME[vin_type], buf[:40])
switch vin_type {
case TXIN_GEN:
rlog.Tracef(10, "Coinbase transaction\n")
if coinbase_done {
return fmt.Errorf("Transaction cannot have multiple coin base transaction\n")
}
var current_vin Txin_gen
current_vin.Height, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Height for Txin_gen vin in Transaction\n")
}
buf = buf[done:]
tx.Vin = append(tx.Vin, current_vin)
coinbase_done = true // we can no longer have coin base
case TXIN_TO_SCRIPT:
panic("TXIN_TO_SCRIPT not implemented")
case TXIN_TO_SCRIPTHASH:
panic("TXIN_TO_SCRIPTHASH not implemented")
case TXIN_TO_KEY:
var current_vin Txin_to_key
// parse Amount
current_vin.Amount, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Amount for Txin_to_key vin in Transaction\n")
}
buf = buf[done:]
//fmt.Printf("Remaining data %x\n", buf[:20]);
mixin_count, done := binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid offset_count for Txin_to_key vin in Transaction\n")
}
buf = buf[done:]
// safety check mixin cannot be larger than say x
if mixin_count > 400 {
return fmt.Errorf("Mixin cannot be larger than 400\n")
}
if Mixin < 0 {
Mixin = int(mixin_count)
}
if Mixin != int(mixin_count) { // all vins must have same mixin
return fmt.Errorf("Different mixin in Transaction\n")
}
//Mixin_input_count += Mixin
for j := uint64(0); j < mixin_count; j++ {
offset, done := binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid key offset for Txin_to_key vin in Transaction\n")
}
buf = buf[done:]
current_vin.Key_offsets = append(current_vin.Key_offsets, offset)
}
Key_offset_count += mixin_count
copy(current_vin.K_image[:], buf[:32]) // copy key image
buf = buf[32:] // consume key image bytes
tx.Vin = append(tx.Vin, current_vin)
// panic("TXIN_TO_KEY not implemented")
default:
panic("Invalid VIN type in Transaction")
}
}
//fmt.Printf("TX before vout %+v\n", tx)
//fmt.Printf("buf before vout length %x\n", buf)
vout_length, done := binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Vout length in Transaction\n")
}
buf = buf[done:]
if vout_length == 0 {
return fmt.Errorf("Vout cannot be zero in Transaction\n")
}
for i := uint64(0); i < vout_length; i++ {
// amount is decoded earlier
amount, done := binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Amount in Transaction\n")
}
buf = buf[done:]
// decode vout type
vout_type := buf[0]
buf = buf[1:] // consume 1 more byte
rlog.Tracef(10, "Vout Amount length %d vout type %d \n", amount, vout_type)
if tx.Version == 1 && amount == 0 { // version 2 can have any amount
return fmt.Errorf("Amount cannot be zero in Transaction\n")
}
switch vout_type {
case TXOUT_TO_SCRIPT:
//fmt.Printf("out to script\n")
panic("TXOUT_TO_SCRIPT not implemented")
case TXOUT_TO_SCRIPTHASH:
//fmt.Printf("out to scripthash\n")
var current_vout Txout_to_scripthash
copy(current_vout.Hash[:], buf[0:32])
tx.Vout = append(tx.Vout, Tx_out{Amount: amount, Target: current_vout})
buf = buf[32:]
//panic("TXOUT_TO_SCRIPTHASH not implemented")
case TXOUT_TO_KEY:
//fmt.Printf("out to key\n")
var current_vout Txout_to_key
copy(current_vout.Key[:], buf[0:32])
buf = buf[32:]
//Mixin_input_count++
tx.Vout = append(tx.Vout, Tx_out{Amount: amount, Target: current_vout})
default:
return fmt.Errorf("Invalid VOUT type in Transaction\n")
}
}
// fmt.Printf("Extra %x\n", buf)
// decode extra
extra_length, done := binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Extra length in Transaction\n")
}
buf = buf[done:]
// BUG extra needs to be processed in a loop till we load all extra fields
//tx.ExtraType = buf[0]
// buf = buf[1:] // consume 1 more byte
// extra_length--
rlog.Tracef(8, "extra len %d have %d \n", extra_length, len(buf))
tx.Extra = buf[:extra_length]
// whatever is leftover is signature
buf = buf[extra_length:] // consume more bytes
switch tx.Version {
case 1: // old style signatures, load value
for i := uint64(0); i < Key_offset_count; i++ {
var s Signature_v1
copy(s.R[:], buf[:32])
copy(s.C[:], buf[32:64])
tx.Signature = append(tx.Signature, s)
buf = buf[SIGNATURE_V1_LENGTH:]
}
case 2:
bufreader := bytes.NewReader(buf)
Mixin -= 1 // one is ours, rest are mixin
tx.RctSignature, err = ringct.ParseRingCtSignature(bufreader, len(tx.Vin), len(tx.Vout), Mixin)
if err != nil {
return err
}
}
/* we must deserialize signature some where else
//fmt.Printf("extra bytes %x\n",buf)
//fmt.Printf("signature len %d should be %d\n",len(buf),len(tx.Vin)*SIGNATURE_V1_LENGTH)
fmt.Printf("signature len %d should be %d\n",len(buf),Key_offset_count*SIGNATURE_V1_LENGTH)
switch tx.Version {
case 1 : // old style signatures, load value
for i := uint64(0); i < Key_offset_count;i++{
var s Signature_v1
copy(s.R[:],buf[:32])
copy(s.C[:],buf[32:64])
tx.Signature = append(tx.Signature, s)
buf = buf[SIGNATURE_V1_LENGTH:]
}
case 2:
tx.Signature_RCT.Type, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid RCT signature in Transaction\n")
}
buf = buf[done:]
switch tx.Signature_RCT.Type {
case 0 : // no signature break
case 1 :
tx.Signature_RCT.TxnFee, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid txn fee in Transaction\n")
}
buf = buf[done:]
fmt.Printf("RCT signature type %d Fee %d\n",tx.Signature_RCT.Type, tx.Signature_RCT.TxnFee)
// how many masked inputs depends on number of masked outouts
for i := (0); i < len(tx.Vout);i++{
// read masked input
var info ECDHinfo
copy(info.Mask[:], buf[0:32])
copy(info.Amount[:], buf[32:64])
tx.Signature_RCT.Amounts = append(tx.Signature_RCT.Amounts, info)
buf = buf[64:]
}
// now parse the public keys
for i := (0); i < len(tx.Vout);i++{
// read masked input
var tmp [32]byte
copy(tmp[:], buf[0:32])
tx.Signature_RCT.OutPK = append(tx.Signature_RCT.OutPK, tmp)
buf = buf[32:]
}
case 2 : // panic("ringct type 2 currently not handled")
default:
panic("unknown signature style")
}
default:
panic("unknown transaction version \n")
}
*/
rlog.Tracef(8, "TX deserialized %+v\n", tx)
/*
data.Local_time = binary.LittleEndian.Uint64( buf[24:], )
data.Local_Port = binary.LittleEndian.Uint32( buf[41:])
_ = data.Network_UUID.UnmarshalBinary(buf[58:58+16])
data.Peer_ID = binary.LittleEndian.Uint64( buf[83:] )
*/
return nil //fmt.Errorf("Done Transaction\n")
}
// calculated prefi has signature
func (tx *Transaction) PrefixHash() {
}
// calculated prefi has signature
func (tx *Transaction) Clear() {
// clean the transaction everything
tx.Version = 0
tx.Unlock_Time = 0
tx.Vin = tx.Vin[:0]
tx.Vout = tx.Vout[:0]
tx.Extra = tx.Extra[:0]
}
func (tx *Transaction) SerializeHeader() []byte {
var serialised_header bytes.Buffer
buf := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(buf, tx.Version)
serialised_header.Write(buf[:n])
n = binary.PutUvarint(buf, tx.Unlock_Time)
serialised_header.Write(buf[:n])
/*if len(tx.Vin) < 1 {
panic("No vins")
}*/
n = binary.PutUvarint(buf, uint64(len(tx.Vin)))
serialised_header.Write(buf[:n])
for _, current_vin := range tx.Vin {
switch current_vin.(type) {
case Txin_gen:
serialised_header.WriteByte(TXIN_GEN)
n = binary.PutUvarint(buf, current_vin.(Txin_gen).Height)
serialised_header.Write(buf[:n])
case Txin_to_key:
serialised_header.WriteByte(TXIN_TO_KEY)
n = binary.PutUvarint(buf, current_vin.(Txin_to_key).Amount)
serialised_header.Write(buf[:n])
// number of Ring member
n = binary.PutUvarint(buf, uint64(len(current_vin.(Txin_to_key).Key_offsets)))
serialised_header.Write(buf[:n])
// write ring members
for _, offset := range current_vin.(Txin_to_key).Key_offsets {
n = binary.PutUvarint(buf, offset)
serialised_header.Write(buf[:n])
}
// dump key image, interface needs a concrete type feor accessing array
cvin := current_vin.(Txin_to_key)
serialised_header.Write(cvin.K_image[:])
}
}
// time to serialize vouts
if len(tx.Vout) < 1 {
panic("No vout")
}
n = binary.PutUvarint(buf, uint64(len(tx.Vout)))
serialised_header.Write(buf[:n])
for _, current_vout := range tx.Vout {
// dump amount
n := binary.PutUvarint(buf, current_vout.Amount)
serialised_header.Write(buf[:n])
switch current_vout.Target.(type) {
case Txout_to_key:
serialised_header.WriteByte(TXOUT_TO_KEY)
target := current_vout.Target.(Txout_to_key)
serialised_header.Write(target.Key[:])
//serialised_header.Write(current_vout.Target.(Txout_to_key).Key[:])
default:
panic("This type of Txout not suppported")
}
}
// dump any extras
n = binary.PutUvarint(buf, uint64(len(tx.Extra)))
serialised_header.Write(buf[:n])
//rlog.Tracef("Extra length %d while serializing\n ", len(tx.Extra))
serialised_header.Write(tx.Extra[:])
return serialised_header.Bytes()
}
// serialize entire transaction include signature
func (tx *Transaction) Serialize() []byte {
header_bytes := tx.SerializeHeader()
base_bytes := tx.RctSignature.SerializeBase()
prunable := tx.RctSignature.SerializePrunable()
buf := append(header_bytes, base_bytes...)
buf = append(buf, prunable...)
return buf
}
/*
func (tx *Transaction) IsCoinbase() (result bool){
// check whether the type is Txin.get
if len(tx.Vin) != 0 { // coinbase transactions have no vin
return
}
if tx.Vout[0].(Target) != 0 { // coinbase transactions have no vin
return
}
}*/