364 lines
10 KiB
Go
364 lines
10 KiB
Go
package dcr
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"math"
|
|
"math/big"
|
|
"strconv"
|
|
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
cfg "github.com/decred/dcrd/chaincfg/v3"
|
|
"github.com/decred/dcrd/dcrec"
|
|
"github.com/decred/dcrd/dcrutil/v3"
|
|
"github.com/decred/dcrd/hdkeychain/v3"
|
|
"github.com/decred/dcrd/txscript/v3"
|
|
"github.com/juju/errors"
|
|
"github.com/martinboehm/btcd/wire"
|
|
"github.com/martinboehm/btcutil/base58"
|
|
"github.com/martinboehm/btcutil/chaincfg"
|
|
"spacecruft.org/spacecruft/blockbook/bchain"
|
|
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
|
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
|
|
)
|
|
|
|
const (
|
|
// MainnetMagic is mainnet network constant
|
|
MainnetMagic wire.BitcoinNet = 0xd9b400f9
|
|
// TestnetMagic is testnet network constant
|
|
TestnetMagic wire.BitcoinNet = 0xb194aa75
|
|
)
|
|
|
|
var (
|
|
// MainNetParams are parser parameters for mainnet
|
|
MainNetParams chaincfg.Params
|
|
// TestNet3Params are parser parameters for testnet
|
|
TestNet3Params chaincfg.Params
|
|
)
|
|
|
|
func init() {
|
|
MainNetParams = chaincfg.MainNetParams
|
|
MainNetParams.Net = MainnetMagic
|
|
MainNetParams.PubKeyHashAddrID = []byte{0x07, 0x3f}
|
|
MainNetParams.ScriptHashAddrID = []byte{0x07, 0x1a}
|
|
|
|
TestNet3Params = chaincfg.TestNet3Params
|
|
TestNet3Params.Net = TestnetMagic
|
|
TestNet3Params.PubKeyHashAddrID = []byte{0x0f, 0x21}
|
|
TestNet3Params.ScriptHashAddrID = []byte{0x0e, 0xfc}
|
|
}
|
|
|
|
// DecredParser handle
|
|
type DecredParser struct {
|
|
*btc.BitcoinParser
|
|
baseParser *bchain.BaseParser
|
|
netConfig *cfg.Params
|
|
}
|
|
|
|
// NewDecredParser returns new DecredParser instance
|
|
func NewDecredParser(params *chaincfg.Params, c *btc.Configuration) *DecredParser {
|
|
d := &DecredParser{
|
|
BitcoinParser: btc.NewBitcoinParser(params, c),
|
|
baseParser: &bchain.BaseParser{},
|
|
}
|
|
|
|
switch d.BitcoinParser.Params.Name {
|
|
case "testnet3":
|
|
d.netConfig = cfg.TestNet3Params()
|
|
default:
|
|
d.netConfig = cfg.MainNetParams()
|
|
}
|
|
return d
|
|
}
|
|
|
|
// GetChainParams contains network parameters for the main Decred network,
|
|
// and the test Decred network.
|
|
func GetChainParams(chain string) *chaincfg.Params {
|
|
var param *chaincfg.Params
|
|
|
|
switch chain {
|
|
case "testnet3":
|
|
param = &TestNet3Params
|
|
default:
|
|
param = &MainNetParams
|
|
}
|
|
|
|
if !chaincfg.IsRegistered(param) {
|
|
if err := chaincfg.Register(param); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return param
|
|
}
|
|
|
|
// ParseBlock parses raw block to our Block struct.
|
|
func (p *DecredParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
|
r := bytes.NewReader(b)
|
|
h := wire.BlockHeader{}
|
|
if err := h.Deserialize(r); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if (h.Version & utils.VersionAuxpow) != 0 {
|
|
if err := utils.SkipAuxpow(r); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var w wire.MsgBlock
|
|
if err := utils.DecodeTransactions(r, 0, wire.WitnessEncoding, &w); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
txs := make([]bchain.Tx, len(w.Transactions))
|
|
for ti, t := range w.Transactions {
|
|
txs[ti] = p.TxFromMsgTx(t, false)
|
|
}
|
|
|
|
return &bchain.Block{
|
|
BlockHeader: bchain.BlockHeader{
|
|
Size: len(b),
|
|
Time: h.Timestamp.Unix(),
|
|
},
|
|
Txs: txs,
|
|
}, nil
|
|
}
|
|
|
|
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
|
func (p *DecredParser) ParseTxFromJson(jsonTx json.RawMessage) (*bchain.Tx, error) {
|
|
var getTxResult GetTransactionResult
|
|
if err := json.Unmarshal([]byte(jsonTx), &getTxResult.Result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vins := make([]bchain.Vin, len(getTxResult.Result.Vin))
|
|
for index, input := range getTxResult.Result.Vin {
|
|
hexData := bchain.ScriptSig{}
|
|
if input.ScriptSig != nil {
|
|
hexData.Hex = input.ScriptSig.Hex
|
|
}
|
|
|
|
vins[index] = bchain.Vin{
|
|
Coinbase: input.Coinbase,
|
|
Txid: input.Txid,
|
|
Vout: input.Vout,
|
|
ScriptSig: hexData,
|
|
Sequence: input.Sequence,
|
|
// Addresses: []string{},
|
|
}
|
|
}
|
|
|
|
vouts := make([]bchain.Vout, len(getTxResult.Result.Vout))
|
|
for index, output := range getTxResult.Result.Vout {
|
|
addr := output.ScriptPubKey.Addresses
|
|
// If nulldata type found make asm field the address data.
|
|
if output.ScriptPubKey.Type == "nulldata" {
|
|
addr = []string{output.ScriptPubKey.Asm}
|
|
}
|
|
|
|
vouts[index] = bchain.Vout{
|
|
ValueSat: *big.NewInt(int64(math.Round(output.Value * 1e8))),
|
|
N: output.N,
|
|
ScriptPubKey: bchain.ScriptPubKey{
|
|
Hex: output.ScriptPubKey.Hex,
|
|
Addresses: addr,
|
|
},
|
|
}
|
|
}
|
|
|
|
tx := &bchain.Tx{
|
|
Hex: getTxResult.Result.Hex,
|
|
Txid: getTxResult.Result.Txid,
|
|
Version: getTxResult.Result.Version,
|
|
LockTime: getTxResult.Result.LockTime,
|
|
BlockHeight: getTxResult.Result.BlockHeight,
|
|
Vin: vins,
|
|
Vout: vouts,
|
|
Confirmations: uint32(getTxResult.Result.Confirmations),
|
|
Time: getTxResult.Result.Time,
|
|
Blocktime: getTxResult.Result.Blocktime,
|
|
}
|
|
|
|
tx.CoinSpecificData = getTxResult.Result.TxExtraInfo
|
|
|
|
return tx, nil
|
|
}
|
|
|
|
// GetAddrDescForUnknownInput returns nil AddressDescriptor.
|
|
func (p *DecredParser) GetAddrDescForUnknownInput(tx *bchain.Tx, input int) bchain.AddressDescriptor {
|
|
return nil
|
|
}
|
|
|
|
// GetAddrDescFromAddress returns internal address representation of a given address.
|
|
func (p *DecredParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) {
|
|
addressByte := []byte(address)
|
|
return bchain.AddressDescriptor(addressByte), nil
|
|
}
|
|
|
|
// GetAddrDescFromVout returns internal address representation of a given transaction output.
|
|
func (p *DecredParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) {
|
|
script, err := hex.DecodeString(output.ScriptPubKey.Hex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
const scriptVersion = 0
|
|
const treasuryEnabled = true
|
|
scriptClass, addresses, _, err := txscript.ExtractPkScriptAddrs(scriptVersion, script,
|
|
p.netConfig, treasuryEnabled)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if scriptClass.String() == "nulldata" {
|
|
if parsedOPReturn := p.BitcoinParser.TryParseOPReturn(script); parsedOPReturn != "" {
|
|
return []byte(parsedOPReturn), nil
|
|
}
|
|
}
|
|
|
|
var addressByte []byte
|
|
for i := range addresses {
|
|
addressByte = append(addressByte, addresses[i].String()...)
|
|
}
|
|
return bchain.AddressDescriptor(addressByte), nil
|
|
}
|
|
|
|
// GetAddressesFromAddrDesc returns addresses obtained from the internal address representation
|
|
func (p *DecredParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
|
var addrs []string
|
|
if addrDesc != nil {
|
|
addrs = append(addrs, string(addrDesc))
|
|
}
|
|
return addrs, true, nil
|
|
}
|
|
|
|
// PackTx packs transaction to byte array using protobuf
|
|
func (p *DecredParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
|
return p.baseParser.PackTx(tx, height, blockTime)
|
|
}
|
|
|
|
// UnpackTx unpacks transaction from protobuf byte array
|
|
func (p *DecredParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|
return p.baseParser.UnpackTx(buf)
|
|
}
|
|
|
|
func (p *DecredParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
|
|
pk := extKey.SerializedPubKey()
|
|
hash := dcrutil.Hash160(pk)
|
|
addr, err := dcrutil.NewAddressPubKeyHash(hash, p.netConfig, dcrec.STEcdsaSecp256k1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.GetAddrDescFromAddress(addr.String())
|
|
}
|
|
|
|
// DeriveAddressDescriptors derives address descriptors from given xpub for
|
|
// listed indexes
|
|
func (p *DecredParser) DeriveAddressDescriptors(xpub string, change uint32,
|
|
indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
|
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
changeExtKey, err := extKey.Child(change)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ad := make([]bchain.AddressDescriptor, len(indexes))
|
|
for i, index := range indexes {
|
|
indexExtKey, err := changeExtKey.Child(index)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ad[i], err = p.addrDescFromExtKey(indexExtKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return ad, nil
|
|
}
|
|
|
|
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for
|
|
// addresses in index range
|
|
func (p *DecredParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32,
|
|
fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
|
if toIndex <= fromIndex {
|
|
return nil, errors.New("toIndex<=fromIndex")
|
|
}
|
|
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
changeExtKey, err := extKey.Child(change)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
|
|
for index := fromIndex; index < toIndex; index++ {
|
|
indexExtKey, err := changeExtKey.Child(index)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return ad, nil
|
|
}
|
|
|
|
// DerivationBasePath returns base path of xpub which whose full format is
|
|
// m/44'/<coin type>'/<account>'/<branch>/<address index>. This function only
|
|
// returns a path up to m/44'/<coin type>'/<account>'/ whereby the rest of the
|
|
// other details (<branch>/<address index>) are populated automatically.
|
|
func (p *DecredParser) DerivationBasePath(xpub string) (string, error) {
|
|
var c string
|
|
cn, depth, err := p.decodeXpub(xpub)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if cn >= hdkeychain.HardenedKeyStart {
|
|
cn -= hdkeychain.HardenedKeyStart
|
|
c = "'"
|
|
}
|
|
|
|
c = strconv.Itoa(int(cn)) + c
|
|
if depth != 3 {
|
|
return "unknown/" + c, nil
|
|
}
|
|
|
|
return "m/44'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
|
|
}
|
|
|
|
func (p *DecredParser) decodeXpub(xpub string) (childNum uint32, depth uint16, err error) {
|
|
decoded := base58.Decode(xpub)
|
|
|
|
// serializedKeyLen is the length of a serialized public or private
|
|
// extended key. It consists of 4 bytes version, 1 byte depth, 4 bytes
|
|
// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
|
|
// public/private key data.
|
|
serializedKeyLen := 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
|
|
if len(decoded) != serializedKeyLen+4 {
|
|
err = errors.New("invalid extended key length")
|
|
return
|
|
}
|
|
|
|
payload := decoded[:len(decoded)-4]
|
|
checkSum := decoded[len(decoded)-4:]
|
|
expectedCheckSum := chainhash.HashB(chainhash.HashB(payload))[:4]
|
|
if !bytes.Equal(checkSum, expectedCheckSum) {
|
|
err = errors.New("bad checksum value")
|
|
return
|
|
}
|
|
|
|
depth = uint16(payload[4:5][0])
|
|
childNum = binary.BigEndian.Uint32(payload[9:13])
|
|
return
|
|
}
|