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 }