github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/firo/firoparser.go (about)

     1  package firo
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"io"
     8  
     9  	"github.com/martinboehm/btcd/chaincfg/chainhash"
    10  	"github.com/martinboehm/btcd/wire"
    11  	"github.com/martinboehm/btcutil/chaincfg"
    12  	"github.com/trezor/blockbook/bchain"
    13  	"github.com/trezor/blockbook/bchain/coins/btc"
    14  )
    15  
    16  const (
    17  	OpZeroCoinMint      = 0xc1
    18  	OpZeroCoinSpend     = 0xc2
    19  	OpSigmaMint         = 0xc3
    20  	OpSigmaSpend        = 0xc4
    21  	OpLelantusMint      = 0xc5
    22  	OpLelantusJMint     = 0xc6
    23  	OpLelantusJoinSplit = 0xc7
    24  	OpLelantusJoinSplitPayload = 0xc9
    25  
    26  	MainnetMagic wire.BitcoinNet = 0xe3d9fef1
    27  	TestnetMagic wire.BitcoinNet = 0xcffcbeea
    28  	RegtestMagic wire.BitcoinNet = 0xfabfb5da
    29  
    30  	GenesisBlockTime                  = 1414776286
    31  	SwitchToMTPBlockHeader            = 1544443200
    32  	SwitchToProgPowBlockHeaderTestnet = 1630069200
    33  	SwitchToProgPowBlockHeaderMainnet = 1635228000
    34  	MTPL                              = 64
    35  
    36  	SpendTxID = "0000000000000000000000000000000000000000000000000000000000000000"
    37  
    38  	TransactionQuorumCommitmentType = 6
    39  )
    40  
    41  var (
    42  	MainNetParams chaincfg.Params
    43  	TestNetParams chaincfg.Params
    44  	RegtestParams chaincfg.Params
    45  )
    46  
    47  func init() {
    48  	// mainnet
    49  	MainNetParams = chaincfg.MainNetParams
    50  	MainNetParams.Net = MainnetMagic
    51  
    52  	MainNetParams.AddressMagicLen = 1
    53  	MainNetParams.PubKeyHashAddrID = []byte{0x52}
    54  	MainNetParams.ScriptHashAddrID = []byte{0x07}
    55  
    56  	// testnet
    57  	TestNetParams = chaincfg.TestNet3Params
    58  	TestNetParams.Net = TestnetMagic
    59  
    60  	TestNetParams.AddressMagicLen = 1
    61  	TestNetParams.PubKeyHashAddrID = []byte{0x41}
    62  	TestNetParams.ScriptHashAddrID = []byte{0xb2}
    63  
    64  	// regtest
    65  	RegtestParams = chaincfg.RegressionNetParams
    66  	RegtestParams.Net = RegtestMagic
    67  }
    68  
    69  // FiroParser handle
    70  type FiroParser struct {
    71  	*btc.BitcoinLikeParser
    72  }
    73  
    74  // NewFiroParser returns new FiroParser instance
    75  func NewFiroParser(params *chaincfg.Params, c *btc.Configuration) *FiroParser {
    76  	return &FiroParser{
    77  		BitcoinLikeParser: btc.NewBitcoinLikeParser(params, c),
    78  	}
    79  }
    80  
    81  // GetChainParams contains network parameters for the main Firo network,
    82  // the regression test Firo network, the test Firo network and
    83  // the simulation test Firo network, in this order
    84  func GetChainParams(chain string) *chaincfg.Params {
    85  	if !chaincfg.IsRegistered(&MainNetParams) {
    86  		err := chaincfg.Register(&MainNetParams)
    87  		if err == nil {
    88  			err = chaincfg.Register(&TestNetParams)
    89  		}
    90  		if err == nil {
    91  			err = chaincfg.Register(&RegtestParams)
    92  		}
    93  		if err != nil {
    94  			panic(err)
    95  		}
    96  	}
    97  	switch chain {
    98  	case "test":
    99  		return &TestNetParams
   100  	case "regtest":
   101  		return &RegtestParams
   102  	default:
   103  		return &MainNetParams
   104  	}
   105  }
   106  
   107  // GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
   108  func (p *FiroParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
   109  
   110  	if len(addrDesc) > 0 {
   111  		switch addrDesc[0] {
   112  		case OpZeroCoinMint:
   113  			return []string{"Zeromint"}, false, nil
   114  		case OpZeroCoinSpend:
   115  			return []string{"Zerospend"}, false, nil
   116  		case OpSigmaMint:
   117  			return []string{"Sigmamint"}, false, nil
   118  		case OpSigmaSpend:
   119  			return []string{"Sigmaspend"}, false, nil
   120  		case OpLelantusMint:
   121  			return []string{"LelantusMint"}, false, nil
   122  		case OpLelantusJMint:
   123  			return []string{"LelantusJMint"}, false, nil
   124  		case OpLelantusJoinSplit:
   125  			return []string{"LelantusJoinSplit"}, false, nil
   126  		case OpLelantusJoinSplitPayload:
   127  			return []string{"LelantusJoinSplit"}, false, nil
   128  		}
   129  	}
   130  
   131  	return p.OutputScriptToAddressesFunc(addrDesc)
   132  }
   133  
   134  // PackTx packs transaction to byte array using protobuf
   135  func (p *FiroParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
   136  	return p.BaseParser.PackTx(tx, height, blockTime)
   137  }
   138  
   139  // UnpackTx unpacks transaction from protobuf byte array
   140  func (p *FiroParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
   141  	return p.BaseParser.UnpackTx(buf)
   142  }
   143  
   144  // TxFromFiroMsgTx converts bitcoin wire Tx to bchain.Tx
   145  func (p *FiroParser) TxFromFiroMsgTx(t *FiroMsgTx, parseAddresses bool) bchain.Tx {
   146  	btx := p.TxFromMsgTx(&t.MsgTx, parseAddresses)
   147  
   148  	// NOTE: wire.MsgTx.TxHash() doesn't include extra
   149  	btx.Txid = t.TxHash().String()
   150  
   151  	return btx
   152  }
   153  
   154  // ParseBlock parses raw block to our Block struct
   155  func (p *FiroParser) ParseBlock(b []byte) (*bchain.Block, error) {
   156  	reader := bytes.NewReader(b)
   157  
   158  	// parse standard block header first
   159  	header, err := parseBlockHeader(reader)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	// then ProgPow or MTP header
   165  	if isProgPow(header, p.Params.Net == TestnetMagic) {
   166  		progPowHeader := ProgPowBlockHeader{}
   167  
   168  		// header
   169  		err = binary.Read(reader, binary.LittleEndian, &progPowHeader)
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  	} else {
   174  		if isMTP(header) {
   175  			mtpHeader := MTPBlockHeader{}
   176  			mtpHashDataRoot := MTPHashDataRoot{}
   177  
   178  			// header
   179  			err = binary.Read(reader, binary.LittleEndian, &mtpHeader)
   180  			if err != nil {
   181  				return nil, err
   182  			}
   183  
   184  			// hash data root
   185  			err = binary.Read(reader, binary.LittleEndian, &mtpHashDataRoot)
   186  			if err != nil {
   187  				return nil, err
   188  			}
   189  
   190  			isAllZero := true
   191  			for i := 0; i < 16; i++ {
   192  				if mtpHashDataRoot.HashRootMTP[i] != 0 {
   193  					isAllZero = false
   194  					break
   195  				}
   196  			}
   197  			
   198  
   199  			if !isAllZero {
   200  				// hash data
   201  				mtpHashData := MTPHashData{}
   202  				err = binary.Read(reader, binary.LittleEndian, &mtpHashData)
   203  				if err != nil {
   204  					return nil, err
   205  				}
   206  
   207  				// proof
   208  				for i := 0; i < MTPL*3; i++ {
   209  					var numberProofBlocks uint8
   210  
   211  					err = binary.Read(reader, binary.LittleEndian, &numberProofBlocks)
   212  					if err != nil {
   213  						return nil, err
   214  					}
   215  
   216  					for j := uint8(0); j < numberProofBlocks; j++ {
   217  						var mtpData [16]uint8
   218  
   219  						err = binary.Read(reader, binary.LittleEndian, mtpData[:])
   220  						if err != nil {
   221  							return nil, err
   222  						}
   223  					}
   224  				}
   225  			}
   226  		}
   227  	}
   228  
   229  	// parse txs
   230  	ntx, err := wire.ReadVarInt(reader, 0)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	txs := make([]bchain.Tx, ntx)
   236  
   237  	for i := uint64(0); i < ntx; i++ {
   238  		tx := FiroMsgTx{}
   239  
   240  		// read version and seek back
   241  		var version uint32 = 0
   242  		if err = binary.Read(reader, binary.LittleEndian, &version); err != nil {
   243  			return nil, err
   244  		}
   245  
   246  		if _, err = reader.Seek(-4, io.SeekCurrent); err != nil {
   247  			return nil, err
   248  		}
   249  
   250  		txVersion := version & 0xffff
   251  		txType := (version >> 16) & 0xffff
   252  
   253  		enc := wire.WitnessEncoding
   254  
   255  		// transaction quorum commitment could not be parsed with witness flag
   256  		if txVersion == 3 && txType == TransactionQuorumCommitmentType {
   257  			enc = wire.BaseEncoding
   258  		}
   259  
   260  		if err = tx.FiroDecode(reader, 0, enc); err != nil {
   261  			return nil, err
   262  		}
   263  
   264  		btx := p.TxFromFiroMsgTx(&tx, false)
   265  
   266  		if err = p.parseFiroTx(&btx); err != nil {
   267  			return nil, err
   268  		}
   269  
   270  		txs[i] = btx
   271  	}
   272  
   273  	return &bchain.Block{
   274  		BlockHeader: bchain.BlockHeader{
   275  			Size: len(b),
   276  			Time: header.Timestamp.Unix(),
   277  		},
   278  		Txs: txs,
   279  	}, nil
   280  }
   281  
   282  // ParseTxFromJson parses JSON message containing transaction and returns Tx struct
   283  func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
   284  	var tx bchain.Tx
   285  	err := json.Unmarshal(msg, &tx)
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  
   290  	for i := range tx.Vout {
   291  		vout := &tx.Vout[i]
   292  		// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
   293  		vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue)
   294  		if err != nil {
   295  			return nil, err
   296  		}
   297  		vout.JsonValue = ""
   298  	}
   299  
   300  	p.parseFiroTx(&tx)
   301  
   302  	return &tx, nil
   303  }
   304  
   305  func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error {
   306  	for i := range tx.Vin {
   307  		vin := &tx.Vin[i]
   308  
   309  		// FIXME: right now we treat zerocoin spend vin as coinbase
   310  		// change this after blockbook support special type of vin
   311  		if vin.Txid == SpendTxID {
   312  			vin.Coinbase = vin.Txid
   313  			vin.Txid = ""
   314  			vin.Sequence = 0
   315  			vin.Vout = 0
   316  		}
   317  	}
   318  
   319  	return nil
   320  }
   321  
   322  func parseBlockHeader(r io.Reader) (*wire.BlockHeader, error) {
   323  	h := &wire.BlockHeader{}
   324  	err := h.Deserialize(r)
   325  	return h, err
   326  }
   327  
   328  func isMTP(h *wire.BlockHeader) bool {
   329  	epoch := h.Timestamp.Unix()
   330  
   331  	// the genesis block never be MTP block
   332  	return epoch > GenesisBlockTime && epoch >= SwitchToMTPBlockHeader
   333  }
   334  
   335  func isProgPow(h *wire.BlockHeader, isTestNet bool) bool {
   336  	epoch := h.Timestamp.Unix()
   337  
   338  	// the genesis block never be MTP block
   339  	return isTestNet && epoch >= SwitchToProgPowBlockHeaderTestnet || !isTestNet && epoch >= SwitchToProgPowBlockHeaderMainnet
   340  }
   341  
   342  type MTPHashDataRoot struct {
   343  	HashRootMTP [16]uint8
   344  }
   345  
   346  type MTPHashData struct {
   347  	BlockMTP    [128][128]uint64
   348  }
   349  
   350  type MTPBlockHeader struct {
   351  	VersionMTP   int32
   352  	MTPHashValue chainhash.Hash
   353  	Reserved1    chainhash.Hash
   354  	Reserved2    chainhash.Hash
   355  }
   356  
   357  type ProgPowBlockHeader struct {
   358  	Nonce64 int64
   359  	MixHash chainhash.Hash
   360  }