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 } 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, } 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} 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 }