github.com/decred/politeia@v1.4.0/politeiawww/legacy/mdstream/mdstream.go (about)

     1  // Copyright (c) 2019-2020 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package mdstream
     6  
     7  import (
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"strconv"
    14  	"strings"
    15  
    16  	pd "github.com/decred/politeia/politeiad/api/v1"
    17  	"github.com/decred/politeia/politeiad/api/v1/identity"
    18  	cms "github.com/decred/politeia/politeiawww/api/cms/v1"
    19  	"github.com/decred/politeia/util"
    20  )
    21  
    22  const (
    23  	// mdstream IDs
    24  	IDInvalid              = 0
    25  	IDRecordStatusChange   = 2
    26  	IDInvoiceGeneral       = 3
    27  	IDInvoiceStatusChange  = 4
    28  	IDInvoicePayment       = 5
    29  	IDDCCGeneral           = 6
    30  	IDDCCStatusChange      = 7
    31  	IDDCCSupportOpposition = 8
    32  
    33  	// mdstream current supported versions
    34  	VersionRecordStatusChange   = 2
    35  	VersionInvoiceGeneral       = 1
    36  	VersionInvoiceStatusChange  = 1
    37  	VersionInvoicePayment       = 1
    38  	VersionDCCGeneral           = 1
    39  	VersionDCCStatusChange      = 1
    40  	VersionDCCSupposeOpposition = 1
    41  )
    42  
    43  // DecodeVersion returns the version of the provided mstream payload. This
    44  // function should only be used when the payload contains a single struct with
    45  // a version field.
    46  func DecodeVersion(payload []byte) (uint, error) {
    47  	data := make(map[string]interface{}, 32)
    48  	err := json.Unmarshal(payload, &data)
    49  	if err != nil {
    50  		return 0, err
    51  	}
    52  	version := uint(data["version"].(float64))
    53  	if version == 0 {
    54  		return 0, fmt.Errorf("version not found")
    55  	}
    56  	return version, nil
    57  }
    58  
    59  // RecordStatusChangeV1 represents a politeiad record status change and is used
    60  // to store additional status change metadata that would not otherwise be
    61  // captured by the politeiad status change routes.
    62  //
    63  // This mdstream is used by both pi and cms.
    64  type RecordStatusChangeV1 struct {
    65  	Version             uint             `json:"version"`                       // Version of the struct
    66  	AdminPubKey         string           `json:"adminpubkey"`                   // Identity of the administrator
    67  	NewStatus           pd.RecordStatusT `json:"newstatus"`                     // New status
    68  	StatusChangeMessage string           `json:"statuschangemessage,omitempty"` // Change message
    69  	Timestamp           int64            `json:"timestamp"`                     // UNIX timestamp
    70  }
    71  
    72  // EncodeRecordStatusChangeV1 encodes an RecordStatusChangeV1 into a JSON byte
    73  // slice.
    74  func EncodeRecordStatusChangeV1(rsc RecordStatusChangeV1) ([]byte, error) {
    75  	b, err := json.Marshal(rsc)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	return b, nil
    80  }
    81  
    82  // RecordStatusChangeV2 represents a politeiad record status change and is used
    83  // to store additional status change metadata that would not otherwise be
    84  // captured by the politeiad status change routes.
    85  //
    86  // V2 adds the Signature field, which was erroneously left out of V1.
    87  //
    88  // This mdstream is used by both pi and cms.
    89  type RecordStatusChangeV2 struct {
    90  	Version             uint             `json:"version"`                       // Struct version
    91  	NewStatus           pd.RecordStatusT `json:"newstatus"`                     // New status
    92  	StatusChangeMessage string           `json:"statuschangemessage,omitempty"` // Change message
    93  	Signature           string           `json:"signature"`                     // Signature of (Token + NewStatus + StatusChangeMessage)
    94  	AdminPubKey         string           `json:"adminpubkey"`                   // Signature pubkey
    95  	Timestamp           int64            `json:"timestamp"`                     // UNIX timestamp
    96  }
    97  
    98  // VerifySignature verifies that the RecordStatusChangeV2 signature is correct.
    99  func (r *RecordStatusChangeV2) VerifySignature(token string) error {
   100  	sig, err := util.ConvertSignature(r.Signature)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	b, err := hex.DecodeString(r.AdminPubKey)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	pk, err := identity.PublicIdentityFromBytes(b)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	msg := token + strconv.Itoa(int(r.NewStatus)) + r.StatusChangeMessage
   113  	if !pk.VerifyMessage([]byte(msg), sig) {
   114  		return fmt.Errorf("invalid signature")
   115  	}
   116  	return nil
   117  }
   118  
   119  // EncodeRecordStatusChangeV2 encodes an RecordStatusChangeV2 into a JSON byte
   120  // slice.
   121  func EncodeRecordStatusChangeV2(rsc RecordStatusChangeV2) ([]byte, error) {
   122  	b, err := json.Marshal(rsc)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return b, nil
   127  }
   128  
   129  // DecodeRecordStatusChanges decodes a JSON byte slice into a slice of
   130  // RecordStatusChangeV1 and a slice of RecordStatusChangeV2.
   131  func DecodeRecordStatusChanges(payload []byte) ([]RecordStatusChangeV1, []RecordStatusChangeV2, error) {
   132  	statusesV1 := make([]RecordStatusChangeV1, 0, 16)
   133  	statusesV2 := make([]RecordStatusChangeV2, 0, 16)
   134  
   135  	d := json.NewDecoder(strings.NewReader(string(payload)))
   136  	for {
   137  		// Decode json into a map so we can determine the version.
   138  		statusChange := make(map[string]interface{}, 6)
   139  		err := d.Decode(&statusChange)
   140  		if errors.Is(err, io.EOF) {
   141  			break
   142  		} else if err != nil {
   143  			return nil, nil, err
   144  		}
   145  
   146  		// These fields exist in all status change versions so they
   147  		// can be converted to their appropriate types outside the
   148  		// switch statement.
   149  		// Note: a JSON number has to first be type cast to float64
   150  		var (
   151  			version   = uint(statusChange["version"].(float64))
   152  			newStatus = pd.RecordStatusT(int(statusChange["newstatus"].(float64)))
   153  			pubkey    = statusChange["adminpubkey"].(string)
   154  			ts        = int64(statusChange["timestamp"].(float64))
   155  		)
   156  
   157  		// The status change message is optional
   158  		var msg string
   159  		m, ok := statusChange["statuschangemessage"]
   160  		if ok {
   161  			msg = m.(string)
   162  		}
   163  
   164  		// Handle different versions
   165  		switch version {
   166  		case 1:
   167  			statusesV1 = append(statusesV1, RecordStatusChangeV1{
   168  				Version:             version,
   169  				NewStatus:           newStatus,
   170  				StatusChangeMessage: msg,
   171  				AdminPubKey:         pubkey,
   172  				Timestamp:           ts,
   173  			})
   174  		case 2:
   175  			statusesV2 = append(statusesV2, RecordStatusChangeV2{
   176  				Version:             version,
   177  				NewStatus:           newStatus,
   178  				StatusChangeMessage: msg,
   179  				AdminPubKey:         pubkey,
   180  				Signature:           statusChange["signature"].(string),
   181  				Timestamp:           ts,
   182  			})
   183  		default:
   184  			return nil, nil, fmt.Errorf("invalid status change version: %v",
   185  				statusChange["version"])
   186  		}
   187  	}
   188  
   189  	return statusesV1, statusesV2, nil
   190  }
   191  
   192  // InvoiceGeneral represents the general metadata for an invoice and is
   193  // stored in the metadata IDInvoiceGeneral in politeiad.
   194  type InvoiceGeneral struct {
   195  	Version   uint64 `json:"version"`   // Version of the struct
   196  	Timestamp int64  `json:"timestamp"` // Last update of invoice
   197  	PublicKey string `json:"publickey"` // Key used for signature
   198  	Signature string `json:"signature"` // Signature of merkle root
   199  }
   200  
   201  // EncodeInvoiceGeneral encodes a InvoiceGeneral into a JSON
   202  // byte slice.
   203  func EncodeInvoiceGeneral(md InvoiceGeneral) ([]byte, error) {
   204  	b, err := json.Marshal(md)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	return b, nil
   210  }
   211  
   212  // DecodeInvoiceGeneral decodes a JSON byte slice into an InvoiceGeneral.
   213  func DecodeInvoiceGeneral(payload []byte) (*InvoiceGeneral, error) {
   214  	var md InvoiceGeneral
   215  
   216  	err := json.Unmarshal(payload, &md)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	return &md, nil
   222  }
   223  
   224  // InvoiceStatusChange represents an invoice status change and is stored
   225  // in the metadata IDInvoiceStatusChange in politeiad.
   226  type InvoiceStatusChange struct {
   227  	Version        uint               `json:"version"`        // Version of the struct
   228  	AdminPublicKey string             `json:"adminpublickey"` // Identity of the administrator
   229  	NewStatus      cms.InvoiceStatusT `json:"newstatus"`      // Status
   230  	Reason         string             `json:"reason"`         // Reason
   231  	Timestamp      int64              `json:"timestamp"`      // Timestamp of the change
   232  }
   233  
   234  // EncodeInvoiceStatusChange encodes a InvoiceStatusChange into a
   235  // JSON byte slice.
   236  func EncodeInvoiceStatusChange(md InvoiceStatusChange) ([]byte, error) {
   237  	b, err := json.Marshal(md)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	return b, nil
   243  }
   244  
   245  // DecodeInvoiceStatusChange decodes a JSON byte slice into a slice of
   246  // InvoiceStatusChanges.
   247  func DecodeInvoiceStatusChange(payload []byte) ([]InvoiceStatusChange, error) {
   248  	var md []InvoiceStatusChange
   249  
   250  	d := json.NewDecoder(strings.NewReader(string(payload)))
   251  	for {
   252  		var m InvoiceStatusChange
   253  		err := d.Decode(&m)
   254  		if errors.Is(err, io.EOF) {
   255  			break
   256  		} else if err != nil {
   257  			return nil, err
   258  		}
   259  
   260  		md = append(md, m)
   261  	}
   262  
   263  	return md, nil
   264  }
   265  
   266  // InvoicePayment represents an invoice payment and is stored
   267  // in the metadata IDInvoicePayment in politeiad.
   268  type InvoicePayment struct {
   269  	Version        uint   `json:"version"`        // Version of the struct
   270  	TxIDs          string `json:"txids"`          // TxIDs captured from the payment, separated by commas
   271  	Timestamp      int64  `json:"timeupdated"`    // Time of last payment update
   272  	AmountReceived int64  `json:"amountreceived"` // Amount of DCR payment currently received
   273  }
   274  
   275  // EncodeInvoicePayment encodes a InvoicePayment into a JSON byte slice.
   276  func EncodeInvoicePayment(md InvoicePayment) ([]byte, error) {
   277  	b, err := json.Marshal(md)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	return b, nil
   283  }
   284  
   285  // DecodeInvoicePayment decodes a JSON byte slice into an InvoicePayment.
   286  func DecodeInvoicePayment(payload []byte) ([]InvoicePayment, error) {
   287  	var md []InvoicePayment
   288  
   289  	d := json.NewDecoder(strings.NewReader(string(payload)))
   290  	for {
   291  		var m InvoicePayment
   292  		err := d.Decode(&m)
   293  		if errors.Is(err, io.EOF) {
   294  			break
   295  		} else if err != nil {
   296  			return nil, err
   297  		}
   298  
   299  		md = append(md, m)
   300  	}
   301  
   302  	return md, nil
   303  }
   304  
   305  // DCCGeneral represents the general metadata for a DCC and is
   306  // stored in the metadata stream IDDCCGeneral in politeiad.
   307  type DCCGeneral struct {
   308  	Version   uint64 `json:"version"`   // Version of the struct
   309  	Timestamp int64  `json:"timestamp"` // Last update of invoice
   310  	PublicKey string `json:"publickey"` // Key used for signature
   311  	Signature string `json:"signature"` // Signature of merkle root
   312  }
   313  
   314  // EncodeDCCGeneral encodes a DCCGeneral into a JSON
   315  // byte slice.
   316  func EncodeDCCGeneral(md DCCGeneral) ([]byte, error) {
   317  	b, err := json.Marshal(md)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	return b, nil
   323  }
   324  
   325  // DecodeDCCGeneral decodes a JSON byte slice into a
   326  // DCCGeneral.
   327  func DecodeDCCGeneral(payload []byte) (*DCCGeneral, error) {
   328  	var md DCCGeneral
   329  
   330  	err := json.Unmarshal(payload, &md)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	return &md, nil
   336  }
   337  
   338  // DCCStatusChange represents the metadata for any status change that
   339  // occurs to a patricular DCC issuance or revocation.
   340  type DCCStatusChange struct {
   341  	Version        uint           `json:"version"`        // Version of the struct
   342  	AdminPublicKey string         `json:"adminpublickey"` // Identity of the administrator
   343  	NewStatus      cms.DCCStatusT `json:"newstatus"`      // Status
   344  	Reason         string         `json:"reason"`         // Reason
   345  	Timestamp      int64          `json:"timestamp"`      // Timestamp of the change
   346  	Signature      string         `json:"signature"`      // Signature of Token + NewStatus + Reason
   347  }
   348  
   349  // EncodeDCCStatusChange encodes a DCCStatusChange into a
   350  // JSON byte slice.
   351  func EncodeDCCStatusChange(md DCCStatusChange) ([]byte, error) {
   352  	b, err := json.Marshal(md)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	return b, nil
   358  }
   359  
   360  // DecodeDCCStatusChange decodes a JSON byte slice into a slice of
   361  // DCCStatusChange.
   362  func DecodeDCCStatusChange(payload []byte) ([]DCCStatusChange, error) {
   363  	var md []DCCStatusChange
   364  
   365  	d := json.NewDecoder(strings.NewReader(string(payload)))
   366  	for {
   367  		var m DCCStatusChange
   368  		err := d.Decode(&m)
   369  		if errors.Is(err, io.EOF) {
   370  			break
   371  		} else if err != nil {
   372  			return nil, err
   373  		}
   374  
   375  		md = append(md, m)
   376  	}
   377  
   378  	return md, nil
   379  }
   380  
   381  // DCCSupportOpposition represents the general metadata for a DCC
   382  // Support/Opposition 'vote' for a given DCC proposal.
   383  type DCCSupportOpposition struct {
   384  	Version   uint64 `json:"version"`   // Version of the struct
   385  	Timestamp int64  `json:"timestamp"` // Last update of invoice
   386  	PublicKey string `json:"publickey"` // Key used for signature
   387  	Vote      string `json:"vote"`      // Vote for support/opposition
   388  	Signature string `json:"signature"` // Signature of Token + Vote
   389  }
   390  
   391  // EncodeDCCSupportOpposition encodes a DCCSupportOpposition into a JSON
   392  // byte slice.
   393  func EncodeDCCSupportOpposition(md DCCSupportOpposition) ([]byte, error) {
   394  	b, err := json.Marshal(md)
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	return b, nil
   400  }
   401  
   402  // DecodeDCCSupportOpposition decodes a JSON byte slice into a
   403  // DCCSupportOpposition.
   404  func DecodeDCCSupportOpposition(payload []byte) ([]DCCSupportOpposition, error) {
   405  	var md []DCCSupportOpposition
   406  	d := json.NewDecoder(strings.NewReader(string(payload)))
   407  	for {
   408  		var m DCCSupportOpposition
   409  		err := d.Decode(&m)
   410  		if errors.Is(err, io.EOF) {
   411  			break
   412  		} else if err != nil {
   413  			return nil, err
   414  		}
   415  
   416  		md = append(md, m)
   417  	}
   418  
   419  	return md, nil
   420  }