lbry.go/schema/claim/claim.go
2020-09-01 13:30:36 -04:00

249 lines
6.1 KiB
Go

package claim
import (
"encoding/hex"
"strconv"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/lbryio/lbryschema.go/address"
legacy_pb "github.com/lbryio/types/v1/go"
pb "github.com/lbryio/types/v2/go"
"github.com/golang/protobuf/proto"
)
type version byte
func (v version) byte() byte {
return byte(v)
}
const (
NoSig = version(byte(0))
//Signature using ECDSA SECP256k1 key and SHA-256 hash.
WithSig = version(byte(1))
UNKNOWN = version(byte(2))
)
type ClaimHelper struct {
*pb.Claim
LegacyClaim *legacy_pb.Claim
ClaimID []byte
Version version
Signature []byte
Payload []byte
}
const migrationErrorMessage = "migration from v1 to v2 protobuf failed with: "
func (c *ClaimHelper) ValidateAddresses(blockchainName string) error {
if c.Claim != nil { // V2
// check the validity of a fee address
if c.Claim.GetStream() != nil {
fee := c.GetStream().GetFee()
if fee != nil {
return validateAddress(fee.GetAddress(), blockchainName)
} else {
return nil
}
} else if c.GetChannel() != nil {
return nil
}
}
return errors.Err("claim helper created with migrated v2 protobuf claim 'invalid state'")
}
func validateAddress(tmp_addr []byte, blockchainName string) error {
if len(tmp_addr) != 25 {
return errors.Err("invalid address length: " + strconv.FormatInt(int64(len(tmp_addr)), 10) + "!")
}
addr := [25]byte{}
for i := range addr {
addr[i] = tmp_addr[i]
}
_, err := address.EncodeAddress(addr, blockchainName)
if err != nil {
return errors.Err(err)
}
return nil
}
func getVersionFromByte(versionByte byte) version {
if versionByte == byte(0) {
return NoSig
} else if versionByte == byte(1) {
return WithSig
}
return UNKNOWN
}
func (c *ClaimHelper) ValidateCertificate() error {
if c.GetChannel() == nil {
return nil
}
_, err := c.GetPublicKey()
if err != nil {
return errors.Err(err)
}
return nil
}
func (c *ClaimHelper) LoadFromBytes(raw_claim []byte, blockchainName string) error {
if c.String() != "" {
return errors.Err("already initialized")
}
if len(raw_claim) < 1 {
return errors.Err("there is nothing to decode")
}
var claim_pb *pb.Claim
var legacy_claim_pb *legacy_pb.Claim
version := getVersionFromByte(raw_claim[0]) //First byte = version
pbPayload := raw_claim[1:]
var claimID []byte
var signature []byte
if version == WithSig {
if len(raw_claim) < 86 {
return errors.Err("signature version indicated by 1st byte but not enough bytes for valid format")
}
claimID = raw_claim[1:21] // channel claimid = next 20 bytes
signature = raw_claim[21:85] // signature = next 64 bytes
pbPayload = raw_claim[85:] // protobuf payload = remaining bytes
}
claim_pb = &pb.Claim{}
err := proto.Unmarshal(pbPayload, claim_pb)
if err != nil {
legacy_claim_pb = &legacy_pb.Claim{}
legacyErr := proto.Unmarshal(raw_claim, legacy_claim_pb)
if legacyErr == nil {
claim_pb, err = migrateV1PBClaim(*legacy_claim_pb)
if err != nil {
return errors.Prefix(migrationErrorMessage, err)
}
if legacy_claim_pb.GetPublisherSignature() != nil {
version = WithSig
claimID = legacy_claim_pb.GetPublisherSignature().GetCertificateId()
signature = legacy_claim_pb.GetPublisherSignature().GetSignature()
}
if legacy_claim_pb.GetCertificate() != nil {
version = NoSig
}
} else {
return err
}
}
*c = ClaimHelper{
Claim: claim_pb,
LegacyClaim: legacy_claim_pb,
ClaimID: claimID,
Version: version,
Signature: signature,
Payload: pbPayload,
}
// Commenting out because of a bug in SDK release allowing empty addresses.
//err = c.ValidateAddresses(blockchainName)
//if err != nil {
// return err
//}
err = c.ValidateCertificate()
if err != nil {
return err
}
return nil
}
func (c *ClaimHelper) LoadFromHexString(claim_hex string, blockchainName string) error {
buf, err := hex.DecodeString(claim_hex)
if err != nil {
return err
}
return c.LoadFromBytes(buf, blockchainName)
}
func DecodeClaimProtoBytes(serialized []byte, blockchainName string) (*ClaimHelper, error) {
claim := &ClaimHelper{&pb.Claim{}, nil, nil, NoSig, nil, nil}
err := claim.LoadFromBytes(serialized, blockchainName)
if err != nil {
return nil, err
}
return claim, nil
}
func DecodeClaimHex(serialized string, blockchainName string) (*ClaimHelper, error) {
claim_bytes, err := hex.DecodeString(serialized)
if err != nil {
return nil, errors.Err(err)
}
return DecodeClaimBytes(claim_bytes, blockchainName)
}
// DecodeClaimBytes take a byte array and tries to decode it to a protobuf claim or migrate it from either json v1,2,3 or pb v1
func DecodeClaimBytes(serialized []byte, blockchainName string) (*ClaimHelper, error) {
helper, err := DecodeClaimProtoBytes(serialized, blockchainName)
if err == nil {
return helper, nil
}
helper = &ClaimHelper{}
//If protobuf fails, try json versions before returning an error.
v1Claim := new(V1Claim)
err = v1Claim.Unmarshal(serialized)
if err != nil {
v2Claim := new(V2Claim)
err := v2Claim.Unmarshal(serialized)
if err != nil {
v3Claim := new(V3Claim)
err := v3Claim.Unmarshal(serialized)
if err != nil {
return nil, errors.Prefix("Claim value has no matching version", err)
}
helper.Claim, err = migrateV3Claim(*v3Claim)
if err != nil {
return nil, errors.Prefix("V3 Metadata Migration Error", err)
}
return helper, nil
}
helper.Claim, err = migrateV2Claim(*v2Claim)
if err != nil {
return nil, errors.Prefix("V2 Metadata Migration Error ", err)
}
return helper, nil
}
helper.Claim, err = migrateV1Claim(*v1Claim)
if err != nil {
return nil, errors.Prefix("V1 Metadata Migration Error ", err)
}
return helper, nil
}
func (c *ClaimHelper) GetStream() *pb.Stream {
if c != nil {
return c.Claim.GetStream()
}
return nil
}
func (c *ClaimHelper) CompileValue() ([]byte, error) {
payload, err := c.serialized()
if err != nil {
return nil, err
}
var value []byte
value = append(value, c.Version.byte())
if c.Version == WithSig {
value = append(value, c.ClaimID...)
value = append(value, c.Signature...)
}
value = append(value, payload...)
return value, nil
}