From 9159c7602d80995074cff8a64b935ef206c1eae3 Mon Sep 17 00:00:00 2001 From: Mark Beamer Jr Date: Sun, 7 Apr 2019 00:29:12 -0400 Subject: [PATCH] added support for proto3 metadata definitions added signing capabilities all with unit tests --- address/decode.go | 4 +- address/validate.go | 14 +- binding/lbryschema-python-binding.go | 78 ----------- build.sh | 1 - claim/claim.go | 186 ++++++++++++++++++--------- claim/claim_test.go | 6 +- claim/decode_test.go | 50 ++++++- claim/keys.go | 50 +++++++ claim/keys_test.go | 36 ++++++ claim/migration.go | 175 +++++++++++++++---------- claim/migration_test.go | 113 ++++++++++++---- claim/schema.go | 2 +- claim/serialization.go | 70 +++++----- claim/sign.go | 112 ++++++++++++++++ claim/sign_test.go | 137 ++++++++++++++++++++ claim/validator.go | 120 +++++++++-------- claim/validator_test.go | 24 +++- test_binding.py | 91 ------------- 18 files changed, 842 insertions(+), 427 deletions(-) delete mode 100644 binding/lbryschema-python-binding.go create mode 100644 claim/keys.go create mode 100644 claim/keys_test.go create mode 100644 claim/sign.go create mode 100644 claim/sign_test.go delete mode 100644 test_binding.py diff --git a/address/decode.go b/address/decode.go index 016ff74..0e3de50 100644 --- a/address/decode.go +++ b/address/decode.go @@ -1,14 +1,14 @@ package address import ( - "errors" + "github.com/lbryio/lbry.go/extras/errors" "github.com/lbryio/lbryschema.go/address/base58" ) func DecodeAddress(address string, blockchainName string) ([addressLength]byte, error) { decoded, err := base58.DecodeBase58(address, addressLength) if err != nil { - return [addressLength]byte{}, errors.New("failed to decode") + return [addressLength]byte{}, errors.Err("failed to decode") } buf := [addressLength]byte{} for i, b := range decoded { diff --git a/address/validate.go b/address/validate.go index f26ca03..74d8d05 100644 --- a/address/validate.go +++ b/address/validate.go @@ -1,15 +1,16 @@ package address import ( - "errors" - + "github.com/lbryio/lbry.go/extras/errors" "github.com/lbryio/lbryschema.go/address/base58" ) const lbrycrdMainPubkeyPrefix = byte(85) const lbrycrdMainScriptPrefix = byte(122) + const lbrycrdTestnetPubkeyPrefix = byte(111) const lbrycrdTestnetScriptPrefix = byte(196) + const lbrycrdRegtestPubkeyPrefix = byte(111) const lbrycrdRegtestScriptPrefix = byte(196) @@ -17,6 +18,7 @@ const prefixLength = 1 const pubkeyLength = 20 const checksumLength = 4 const addressLength = prefixLength + pubkeyLength + checksumLength + const lbrycrdMain = "lbrycrd_main" const lbrycrdTestnet = "lbrycrd_testnet" const lbrycrdRegtest = "lbrycrd_regtest" @@ -52,16 +54,16 @@ func ChecksumIsValid(address [addressLength]byte) bool { func ValidateAddress(address [addressLength]byte, blockchainName string) ([addressLength]byte, error) { if blockchainName != lbrycrdMain && blockchainName != lbrycrdTestnet && blockchainName != lbrycrdRegtest { - return address, errors.New("invalid blockchain name") + return address, errors.Err("invalid blockchain name") } if !PrefixIsValid(address, blockchainName) { - return address, errors.New("invalid prefix") + return address, errors.Err("invalid prefix") } if !PubKeyIsValid(address) { - return address, errors.New("invalid pubkey") + return address, errors.Err("invalid pubkey") } if !ChecksumIsValid(address) { - return address, errors.New("invalid address checksum") + return address, errors.Err("invalid address checksum") } return address, nil } diff --git a/binding/lbryschema-python-binding.go b/binding/lbryschema-python-binding.go deleted file mode 100644 index 0fdb63a..0000000 --- a/binding/lbryschema-python-binding.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "C" - "encoding/hex" - "github.com/lbryio/lbryschema.go/address" - "github.com/lbryio/lbryschema.go/claim" -) - -//export VerifySignature -func VerifySignature(claimHex string, certificateHex string, claimAddress string, certificateId string, blockchainName string) bool { - decodedClaim, err := claim.DecodeClaimHex(claimHex, blockchainName) - if err != nil { - return false - } - decodedCertificate, err := claim.DecodeClaimHex(certificateHex, blockchainName) - if err != nil { - return false - } - result, err := decodedClaim.ValidateClaimSignature(decodedCertificate, claimAddress, certificateId, blockchainName) - if err != nil { - return false - } - return result -} - -//export DecodeClaimHex -func DecodeClaimHex(claimHex string, blockchainName string) *C.char { - decodedClaim, err := claim.DecodeClaimHex(claimHex, blockchainName) - if err != nil { - return C.CString("decode error: " + err.Error()) - } - decoded, err := decodedClaim.RenderJSON() - if err != nil { - return C.CString("encode error: " + err.Error()) - } - return C.CString(decoded) -} - -//export SerializeClaimFromJSON -func SerializeClaimFromJSON(claimJSON string, blockchainName string) *C.char { - decodedClaim, err := claim.DecodeClaimJSON(claimJSON, blockchainName) - if err != nil { - return C.CString("decode error: " + err.Error()) - } - SerializedHex, err := decodedClaim.SerializedHexString() - if err != nil { - return C.CString("encode error: " + err.Error()) - } - return C.CString(SerializedHex) -} - -//export DecodeAddress -func DecodeAddress(addressString string, blockchainName string) *C.char { - addressBytes, err := address.DecodeAddress(addressString, blockchainName) - if err != nil { - return C.CString("error: " + err.Error()) - } - return C.CString(hex.EncodeToString(addressBytes[:])) -} - -//export EncodeAddress -func EncodeAddress(addressChars string, blockchainName string) *C.char { - addressBytes := [25]byte{} - if len(addressChars) != 25 { - return C.CString("error: address is not 25 bytes") - } - for i := range addressBytes { - addressBytes[i] = byte(addressChars[i]) - } - encodedAddress, err := address.EncodeAddress(addressBytes, blockchainName) - if err != nil { - return C.CString("error: " + err.Error()) - } - return C.CString(encodedAddress) -} - -func main() {} diff --git a/build.sh b/build.sh index 95dae49..de13e86 100755 --- a/build.sh +++ b/build.sh @@ -3,4 +3,3 @@ set -euxo pipefail go build ./... go build ./cli/lbryschema-cli.go -go build -o lbryschema-python-binding.so -buildmode=c-shared ./binding/lbryschema-python-binding.go diff --git a/claim/claim.go b/claim/claim.go index 4e665df..ecb6727 100644 --- a/claim/claim.go +++ b/claim/claim.go @@ -2,53 +2,90 @@ package claim import ( "encoding/hex" + "strconv" - "github.com/golang/protobuf/jsonpb" - "github.com/golang/protobuf/proto" - - "github.com/lbryio/lbry.go/errors" + "github.com/lbryio/lbry.go/extras/errors" "github.com/lbryio/lbryschema.go/address" - "github.com/lbryio/types/go" + 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 { - // check the validity of a fee address - if c.GetClaimType() == pb.Claim_streamType { - fee := c.GetStream().GetMetadata().GetFee() - if fee != nil { - tmp_addr := fee.GetAddress() - if len(tmp_addr) != 25 { - return errors.Err("invalid address length: " + string(len(tmp_addr)) + "!") - } - addr := [25]byte{} - for i := range addr { - addr[i] = tmp_addr[i] - } - _, err := address.EncodeAddress(addr, blockchainName) - if err != nil { - return errors.Err(err) + 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 nil + + return errors.Err("claim helper created with migrated v2 protobuf claim 'invalid state'") } -func (c *ClaimHelper) ValidateCertificate() error { - certificate := c.GetCertificate() - if certificate == nil { - return nil +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) + "!") } - keyType := certificate.GetKeyType() - _, err := c.GetCertificatePublicKey() + addr := [25]byte{} + for i := range addr { + addr[i] = tmp_addr[i] + } + _, err := address.EncodeAddress(addr, blockchainName) if err != nil { return errors.Err(err) } - if keyType.String() != SECP256k1 { - return errors.Err("wrong curve: " + keyType.String()) + + 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 } @@ -61,12 +98,52 @@ func (c *ClaimHelper) LoadFromBytes(raw_claim []byte, blockchainName string) err return errors.Err("there is nothing to decode") } - claim_pb := &pb.Claim{} - err := proto.Unmarshal(raw_claim, claim_pb) - if err != nil { - return err + 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 } - *c = ClaimHelper{claim_pb} + + 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 + 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 @@ -88,7 +165,7 @@ func (c *ClaimHelper) LoadFromHexString(claim_hex string, blockchainName string) } func DecodeClaimProtoBytes(serialized []byte, blockchainName string) (*ClaimHelper, error) { - claim := &ClaimHelper{&pb.Claim{}} + claim := &ClaimHelper{&pb.Claim{}, nil, nil, NoSig, nil} err := claim.LoadFromBytes(serialized, blockchainName) if err != nil { return nil, err @@ -99,21 +176,12 @@ func DecodeClaimProtoBytes(serialized []byte, blockchainName string) (*ClaimHelp func DecodeClaimHex(serialized string, blockchainName string) (*ClaimHelper, error) { claim_bytes, err := hex.DecodeString(serialized) if err != nil { - return nil, err + return nil, errors.Err(err) } return DecodeClaimBytes(claim_bytes, blockchainName) } -func DecodeClaimJSON(claimJSON string, blockchainName string) (*ClaimHelper, error) { - c := &pb.Claim{} - err := jsonpb.UnmarshalString(claimJSON, c) - if err != nil { - return nil, err - } - return &ClaimHelper{c}, nil -} - -// DecodeClaimBytes take a byte array and tries to decode it to a protobuf claim or migrate it from either json v1,2,3 +// 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 { @@ -130,7 +198,7 @@ func DecodeClaimBytes(serialized []byte, blockchainName string) (*ClaimHelper, e v3Claim := new(V3Claim) err := v3Claim.Unmarshal(serialized) if err != nil { - return nil, errors.Prefix("Claim value has no matching verion", err) + return nil, errors.Prefix("Claim value has no matching version", err) } helper.Claim, err = migrateV3Claim(*v3Claim) if err != nil { @@ -154,21 +222,23 @@ func DecodeClaimBytes(serialized []byte, blockchainName string) (*ClaimHelper, e func (c *ClaimHelper) GetStream() *pb.Stream { if c != nil { - return c.Stream + return c.Claim.GetStream() } return nil } -func (c *ClaimHelper) GetCertificate() *pb.Certificate { - if c != nil { - return c.Certificate +func (c *ClaimHelper) CompileValue() ([]byte, error) { + payload, err := c.serialized() + if err != nil { + return nil, err } - return nil -} + 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...) -func (c *ClaimHelper) GetPublisherSignature() *pb.Signature { - if c != nil { - return c.PublisherSignature - } - return nil + return value, nil } diff --git a/claim/claim_test.go b/claim/claim_test.go index 7de9ace..493a5df 100644 --- a/claim/claim_test.go +++ b/claim/claim_test.go @@ -13,15 +13,15 @@ func TestClaimHelper(t *testing.T) { t.Error(err) } - _, err = helper.Serialized() + _, err = helper.serialized() if err != nil { t.Error(err) } - _, err = helper.SerializedHexString() + _, err = helper.serializedHexString() if err != nil { t.Error(err) } - _, err = helper.SerializedNoSignature() + _, err = helper.serializedNoSignature() if err != nil { t.Error(err) } diff --git a/claim/decode_test.go b/claim/decode_test.go index fbe5033..4aa1c1c 100644 --- a/claim/decode_test.go +++ b/claim/decode_test.go @@ -3,8 +3,17 @@ package claim import ( "encoding/hex" "testing" + + pb "github.com/lbryio/types/v2/go" + + "github.com/btcsuite/btcd/btcec" ) +type rawClaim struct { + Hex string + ClaimID string +} + var raw_claims = []string{ "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367", "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65", @@ -23,7 +32,7 @@ func TestDecodeClaims(t *testing.T) { if err != nil { t.Error(err) } - serializedHex, err := claim.SerializedHexString() + serializedHex, err := claim.serializedHexString() if err != nil { t.Error(err) } @@ -40,7 +49,7 @@ func TestStripSignature(t *testing.T) { if err != nil { t.Error(err) } - noSig, err := claim.SerializedNoSignature() + noSig, err := claim.serializedNoSignature() if err != nil { t.Error(err) } @@ -48,3 +57,40 @@ func TestStripSignature(t *testing.T) { t.Error("failed to remove signature") } } + +func TestCreateChannelClaim(t *testing.T) { + private, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Error(err) + } + pubKeyBytes, err := PublicKeyToDER(private.PubKey()) + if err != nil { + t.Error(err) + } + claim := &ClaimHelper{Claim: newChannelClaim(), Version: NoSig} + claim.GetChannel().PublicKey = pubKeyBytes + claim.GetChannel().Title = "Test Channel Title" + claim.GetChannel().Description = "Test Channel Description" + claim.GetChannel().CoverUrl = "http://testcoverurl.com" + claim.GetChannel().Tags = []string{"TagA", "TagB", "TagC"} + claim.GetChannel().Languages = []*pb.Language{{Language: pb.Language_en}, {Language: pb.Language_es}} + claim.GetChannel().ThumbnailUrl = "http://thumbnailurl.com" + claim.GetChannel().ContactEmail = "test@test.com" + claim.GetChannel().HomepageUrl = "http://homepageurl.com" + claim.GetChannel().Locations = []*pb.Location{{Country: pb.Location_AD}, {Country: pb.Location_US, State: "NJ", City: "some city"}} + + rawClaim, err := claim.CompileValue() + if err != nil { + t.Error(err) + } + + claim, err = DecodeClaimBytes(rawClaim, "lbrycrd_main") + if err != nil { + t.Error(err) + } + + if bytes, err := claim.CompileValue(); err != nil || len(bytes) != len(rawClaim) { + t.Error("decoded claim does not match original") + } + +} diff --git a/claim/keys.go b/claim/keys.go new file mode 100644 index 0000000..cb3301e --- /dev/null +++ b/claim/keys.go @@ -0,0 +1,50 @@ +package claim + +import ( + "crypto/elliptic" + "crypto/x509/pkix" + "encoding/asn1" + + "github.com/lbryio/lbry.go/extras/errors" + + "github.com/btcsuite/btcd/btcec" +) + +func PublicKeyToDER(publicKey *btcec.PublicKey) ([]byte, error) { + var publicKeyBytes []byte + var publicKeyAlgorithm pkix.AlgorithmIdentifier + var err error + pub := publicKey.ToECDSA() + publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y) + //ans1 encoding oid for ecdsa public key https://github.com/golang/go/blob/release-branch.go1.12/src/crypto/x509/x509.go#L457 + publicKeyAlgorithm.Algorithm = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + //asn1 encoding oid for secp256k1 https://github.com/bitpay/bitpay-go/blob/v2.2.2/key_utils/key_utils.go#L30 + paramBytes, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 10}) + if err != nil { + return nil, errors.Err(err) + } + publicKeyAlgorithm.Parameters.FullBytes = paramBytes + + return asn1.Marshal(publicKeyInfo{ + Algorithm: publicKeyAlgorithm, + PublicKey: asn1.BitString{ + Bytes: publicKeyBytes, + BitLength: 8 * len(publicKeyBytes), + }, + }) + +} + +func (c *ClaimHelper) GetPublicKey() (*btcec.PublicKey, error) { + if c.GetChannel() == nil { + return nil, errors.Err("claim is not of type channel, so there is no public key to get") + } + return getPublicKeyFromBytes(c.GetChannel().PublicKey) +} + +func getPublicKeyFromBytes(pubKeyBytes []byte) (*btcec.PublicKey, error) { + PKInfo := publicKeyInfo{} + asn1.Unmarshal(pubKeyBytes, &PKInfo) + pubkeyBytes1 := []byte(PKInfo.PublicKey.Bytes) + return btcec.ParsePubKey(pubkeyBytes1, btcec.S256()) +} diff --git a/claim/keys_test.go b/claim/keys_test.go new file mode 100644 index 0000000..0a5e4f6 --- /dev/null +++ b/claim/keys_test.go @@ -0,0 +1,36 @@ +package claim + +import ( + "testing" + + "gotest.tools/assert" +) + +// The purpose of this test, is to make sure the function converts btcec.PublicKey to DER format the same way +// lbry SDK does as this is the bytes that are put into protobuf and the same bytes are used for verify signatures. +// Making sure these +func TestPublicKeyToDER(t *testing.T) { + cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367" + cert_claim, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + p1, err := getPublicKeyFromBytes(cert_claim.Claim.GetChannel().PublicKey) + if err != nil { + t.Error(err) + } + + pubkeyBytes2, err := PublicKeyToDER(p1) + if err != nil { + t.Error(err) + } + for i, b := range cert_claim.Claim.GetChannel().PublicKey { + assert.Assert(t, b == pubkeyBytes2[i], "DER format in bytes must match!") + } + + p2, err := getPublicKeyFromBytes(pubkeyBytes2) + if err != nil { + t.Error(err) + } + assert.Assert(t, p1.IsEqual(p2), "The keys produced must be the same key!") +} diff --git a/claim/migration.go b/claim/migration.go index 79e3a99..418ee8b 100644 --- a/claim/migration.go +++ b/claim/migration.go @@ -3,121 +3,155 @@ package claim import ( "encoding/hex" + "github.com/lbryio/lbry.go/extras/errors" + v1pb "github.com/lbryio/types/v1/go" + pb "github.com/lbryio/types/v2/go" + "github.com/btcsuite/btcutil/base58" - "github.com/lbryio/types/go" ) const lbrySDHash = "lbry_sd_hash" -func newClaim() *pb.Claim { - pbClaim := new(pb.Claim) +func newStreamClaim() *pb.Claim { + claimStream := new(pb.Claim_Stream) stream := new(pb.Stream) - metadata := new(pb.Metadata) - source := new(pb.Source) - pubSig := new(pb.Signature) - fee := new(pb.Fee) - metadata.Fee = fee - stream.Metadata = metadata - stream.Source = source - pbClaim.Stream = stream - pbClaim.PublisherSignature = pubSig + stream.File = new(pb.File) - //Fee version - feeVersion := pb.Fee__0_0_1 - pbClaim.GetStream().GetMetadata().GetFee().Version = &feeVersion - //Metadata version - mdVersion := pb.Metadata__0_1_0 - pbClaim.GetStream().GetMetadata().Version = &mdVersion - //Source version - srcVersion := pb.Source__0_0_1 - pbClaim.GetStream().GetSource().Version = &srcVersion - //Stream version - streamVersion := pb.Stream__0_0_1 - pbClaim.GetStream().Version = &streamVersion - //Claim version - clmVersion := pb.Claim__0_0_1 - pbClaim.Version = &clmVersion - //Claim type - clmType := pb.Claim_streamType - pbClaim.ClaimType = &clmType + pbClaim := new(pb.Claim) + pbClaim.Type = claimStream + claimStream.Stream = stream return pbClaim } -func setMetaData(claim pb.Claim, author string, description string, language pb.Metadata_Language, license string, - licenseURL *string, title string, thumbnail *string, nsfw bool) { - claim.GetStream().GetMetadata().Author = &author - claim.GetStream().GetMetadata().Description = &description - claim.GetStream().GetMetadata().Language = &language - claim.GetStream().GetMetadata().License = &license - claim.GetStream().GetMetadata().Title = &title - claim.GetStream().GetMetadata().Thumbnail = thumbnail - claim.GetStream().GetMetadata().Nsfw = &nsfw - claim.GetStream().GetMetadata().LicenseUrl = licenseURL +func newChannelClaim() *pb.Claim { + claimChannel := new(pb.Claim_Channel) + channel := new(pb.Channel) + pbClaim := new(pb.Claim) + pbClaim.Type = claimChannel + claimChannel.Channel = channel + + return pbClaim +} + +func setMetaData(claim pb.Claim, author string, description string, language pb.Language_Language, license string, + licenseURL *string, title string, thumbnail *string, nsfw bool) { + claim.GetStream().Author = author + claim.GetStream().Description = description + claim.GetStream().Languages = []*pb.Language{{Language: language}} + claim.GetStream().Title = title + if thumbnail != nil { + claim.GetStream().ThumbnailUrl = *thumbnail + } + claim.GetStream().Tags = []string{"mature"} + claim.GetStream().License = license + if licenseURL != nil { + claim.GetStream().LicenseUrl = *licenseURL + } +} + +func migrateV1PBClaim(vClaim v1pb.Claim) (*pb.Claim, error) { + if *vClaim.ClaimType == v1pb.Claim_streamType { + return migrateV1PBStream(vClaim) + } + if *vClaim.ClaimType == v1pb.Claim_certificateType { + return migrateV1PBChannel(vClaim) + } + return nil, errors.Err("Could not migrate v1 protobuf claim due to unknown type '%s'.", vClaim.ClaimType.String()) +} + +func migrateV1PBStream(vClaim v1pb.Claim) (*pb.Claim, error) { + claim := newStreamClaim() + claim.GetStream().MediaType = vClaim.GetStream().GetSource().GetContentType() + md := vClaim.GetStream().GetMetadata() + if md.GetFee() != nil { + claim.GetStream().Fee = new(pb.Fee) + claim.GetStream().GetFee().Amount = uint64(*md.GetFee().Amount * 100000000) + claim.GetStream().GetFee().Address = md.GetFee().GetAddress() + claim.GetStream().GetFee().Currency = pb.Fee_Currency(pb.Fee_Currency_value[md.GetFee().GetCurrency().String()]) + } + claim.GetStream().SdHash = vClaim.GetStream().GetSource().GetSource() + if vClaim.GetStream().GetMetadata().GetNsfw() { + claim.GetStream().Tags = []string{"mature"} + } + claim.GetStream().ThumbnailUrl = md.GetThumbnail() + language := pb.Language_Language(pb.Language_Language_value[md.GetLanguage().String()]) + claim.GetStream().Languages = []*pb.Language{{Language: language}} + claim.GetStream().LicenseUrl = md.GetLicenseUrl() + claim.GetStream().License = md.GetLicense() + claim.GetStream().Title = md.GetTitle() + claim.GetStream().Description = md.GetDescription() + claim.GetStream().Author = md.GetAuthor() + + return claim, nil +} + +func migrateV1PBChannel(vClaim v1pb.Claim) (*pb.Claim, error) { + claim := newChannelClaim() + claim.GetChannel().PublicKey = vClaim.GetCertificate().PublicKey + + return claim, nil } func migrateV1Claim(vClaim V1Claim) (*pb.Claim, error) { - pbClaim := newClaim() - //Not part of json V1 - pbClaim.PublisherSignature = nil + pbClaim := newStreamClaim() //Stream // -->Universal setFee(vClaim.Fee, pbClaim) // -->MetaData - language := pb.Metadata_Language(pb.Metadata_Language_value[vClaim.Language]) + language := pb.Language_Language(pb.Language_Language_value[vClaim.Language]) setMetaData(*pbClaim, vClaim.Author, vClaim.Description, language, vClaim.License, nil, vClaim.Title, vClaim.Thumbnail, false) // -->Source - pbClaim.GetStream().GetSource().ContentType = &vClaim.ContentType - sourceType := pb.Source_SourceTypes(pb.Source_SourceTypes_value[lbrySDHash]) - pbClaim.GetStream().GetSource().SourceType = &sourceType + pbClaim.GetStream().MediaType = vClaim.ContentType src, err := hex.DecodeString(vClaim.Sources.LbrySDHash) - pbClaim.GetStream().GetSource().Source = src + if err != nil { + return nil, errors.Err(err) + } + pbClaim.GetStream().SdHash = src - return pbClaim, err + return pbClaim, nil } func migrateV2Claim(vClaim V2Claim) (*pb.Claim, error) { - pbClaim := newClaim() - //Not part of json V2 - pbClaim.PublisherSignature = nil + pbClaim := newStreamClaim() //Stream // -->Fee setFee(vClaim.Fee, pbClaim) // -->MetaData - language := pb.Metadata_Language(pb.Metadata_Language_value[vClaim.Language]) + language := pb.Language_Language(pb.Language_Language_value[vClaim.Language]) setMetaData(*pbClaim, vClaim.Author, vClaim.Description, language, vClaim.License, vClaim.LicenseURL, vClaim.Title, vClaim.Thumbnail, vClaim.NSFW) // -->Source - pbClaim.GetStream().GetSource().ContentType = &vClaim.ContentType - sourceType := pb.Source_SourceTypes(pb.Source_SourceTypes_value[lbrySDHash]) - pbClaim.GetStream().GetSource().SourceType = &sourceType + pbClaim.GetStream().MediaType = vClaim.ContentType src, err := hex.DecodeString(vClaim.Sources.LbrySDHash) - pbClaim.GetStream().GetSource().Source = src + if err != nil { + return nil, errors.Err(err) + } + pbClaim.GetStream().SdHash = src - return pbClaim, err + return pbClaim, nil } func migrateV3Claim(vClaim V3Claim) (*pb.Claim, error) { - pbClaim := newClaim() - //Not part of json V3 - pbClaim.PublisherSignature = nil + pbClaim := newStreamClaim() //Stream // -->Fee setFee(vClaim.Fee, pbClaim) // -->MetaData - language := pb.Metadata_Language(pb.Metadata_Language_value[vClaim.Language]) + language := pb.Language_Language(pb.Language_Language_value[vClaim.Language]) setMetaData(*pbClaim, vClaim.Author, vClaim.Description, language, vClaim.License, vClaim.LicenseURL, vClaim.Title, vClaim.Thumbnail, vClaim.NSFW) // -->Source - pbClaim.GetStream().GetSource().ContentType = &vClaim.ContentType - sourceType := pb.Source_SourceTypes(pb.Source_SourceTypes_value[lbrySDHash]) - pbClaim.GetStream().GetSource().SourceType = &sourceType + pbClaim.GetStream().MediaType = vClaim.ContentType src, err := hex.DecodeString(vClaim.Sources.LbrySDHash) - pbClaim.GetStream().GetSource().Source = src + if err != nil { + return nil, errors.Err(err) + } + pbClaim.GetStream().SdHash = src - return pbClaim, err + return pbClaim, nil } func setFee(fee *Fee, pbClaim *pb.Claim) { @@ -138,9 +172,10 @@ func setFee(fee *Fee, pbClaim *pb.Claim) { currency = pb.Fee_USD address = fee.USD.Address } + pbClaim.GetStream().Fee = new(pb.Fee) //Fee Settings - pbClaim.GetStream().GetMetadata().GetFee().Amount = &amount - pbClaim.GetStream().GetMetadata().GetFee().Currency = ¤cy - pbClaim.GetStream().GetMetadata().GetFee().Address = base58.Decode(address) + pbClaim.GetStream().GetFee().Amount = uint64(amount * 100000000) + pbClaim.GetStream().GetFee().Currency = currency + pbClaim.GetStream().GetFee().Address = base58.Decode(address) } } diff --git a/claim/migration_test.go b/claim/migration_test.go index 4e291e1..742bb35 100644 --- a/claim/migration_test.go +++ b/claim/migration_test.go @@ -1,11 +1,15 @@ package claim import ( + "bytes" "encoding/hex" "fmt" "testing" + "github.com/lbryio/lbryschema.go/address" + "github.com/btcsuite/btcutil/base58" + "gotest.tools/assert" ) type valueTestPair struct { @@ -51,7 +55,7 @@ var jsonVersionTests = []valueTestPair{ "LBC", "bPwGA9h7uijoy5uAvzVPQw9QyLoYZehHJo", "application/octet-stream", - "UNKNOWN_LANGUAGE", //"English" is not supported for conversion. + "", //"English" is not supported for conversion. "bd94033d13f4f3908708701caf565bfa09cfadf2f34fadf4a73fb86b295d1b21a7e64805994e45b5fbc650f30bac4874", "/homerobert/lbry/speed.jpg", false, @@ -68,7 +72,7 @@ var jsonVersionTests = []valueTestPair{ "LBC", "bLVs3ifPruyZnpYmFfT2TLAmhqZvgjpQDa", "video/quicktime", - "en", + "language:en ", "dcc1bf28893a5037eab9e4a9cd7a4bfe6f76ad6c21970ea4ceee0122f502ef079657d4dca456b4be3249849c4e1868b8", "http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg", false, @@ -85,7 +89,7 @@ var jsonVersionTests = []valueTestPair{ "UNKNOWN_CURRENCY", "", "video/mp4", - "en", + "language:en ", "799a1de93b8e556d7f668103a6ffc48ac5fd6801dd4d89cae6773c80d81c283710fd4fd25ed68d0dbeee268f82914145", "http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg", false, @@ -102,7 +106,7 @@ var jsonVersionTests = []valueTestPair{ "USD", "bMHmZKZbPq6bPBEQFc8MXpiDhF9f7MVxMR", "video/mp4", - "en", + "language:en ", "2bd8d9dd1a218c7f56717e53fa510efd5a8c089ed1f2675a0f8d0b5b8bb3c1ed383cb9f3aeb9b891789761305293979a", "", false, @@ -119,7 +123,7 @@ var jsonVersionTests = []valueTestPair{ "LBC", "bVPqWwYfvjBHYBouuvknbQMXUZFvLdEs5M", "text/plain", - "en", + "language:en ", "7c21ee237324e5a50a3425620fe6cc400d3cccc05519867cda1b9c10a977194e31200414d87146bff470bab7f7d75478", "", false, @@ -136,7 +140,7 @@ var jsonVersionTests = []valueTestPair{ "USD", "bHSe3KAvtVSR4m6S11zduuF9XHtwscDjoE", "audio/mpeg", - "en", + "language:en ", "340e1dda0e8414c21fafbb1f28f2c8b384821fe0d1e2a7143a4810545e64e26a4105042d4d47dc9754b618ecdfe0d191", "http://i.imgur.com/lyKEHZc.jpg", false, @@ -153,7 +157,7 @@ var jsonVersionTests = []valueTestPair{ "LBC", "bRTxtCUpj6TvJHgWcRsGcHaFyrRLkkiXgG", "video/mp4", - "en", + "language:en ", "fc0dac5cfc526354963ff1769f6e739c4e42a0790420ffab9fa3b6401e93ae5a0515eff960f8c9c272907eda6fcba254", "", true, @@ -184,40 +188,95 @@ func TestMigrationFromJSON(t *testing.T) { if err != nil { t.Error("Decode error: ", err) } - if helper.Claim.GetStream().GetMetadata().GetAuthor() != pair.Claim.Author { - t.Error("Author mismatch: expected", pair.Claim.Author, "got", helper.Claim.GetStream().GetMetadata().GetAuthor()) + if helper.Claim.GetStream().GetAuthor() != pair.Claim.Author { + t.Error("Author mismatch: expected", pair.Claim.Author, "got", helper.Claim.GetStream().GetAuthor()) } - if helper.Claim.GetStream().GetMetadata().GetTitle() != pair.Claim.Title { - t.Error("Title mismatch: expected", pair.Claim.Title, "got", helper.Claim.GetStream().GetMetadata().GetTitle()) + if helper.Claim.GetStream().GetTitle() != pair.Claim.Title { + t.Error("Title mismatch: expected", pair.Claim.Title, "got", helper.Claim.GetStream().GetTitle()) } - if helper.Claim.GetStream().GetMetadata().GetDescription() != pair.Claim.Description { - t.Error("Description mismatch: expected", pair.Claim.Description, "got", helper.Claim.GetStream().GetMetadata().GetDescription()) + if helper.Claim.GetStream().GetDescription() != pair.Claim.Description { + t.Error("Description mismatch: expected", pair.Claim.Description, "got", helper.Claim.GetStream().GetDescription()) } - if helper.Claim.GetStream().GetMetadata().GetLicense() != pair.Claim.License { - t.Error("License mismatch: expected", pair.Claim.License, "got", helper.Claim.GetStream().GetMetadata().GetLicense()) + if helper.Claim.GetStream().GetLicense() != pair.Claim.License { + t.Error("License mismatch: expected", pair.Claim.License, "got", helper.Claim.GetStream().GetLicense()) } - if helper.Claim.GetStream().GetMetadata().GetLicenseUrl() != pair.Claim.LicenseURL { - t.Error("LicenseURL mismatch: expected", pair.Claim.LicenseURL, "got", helper.Claim.GetStream().GetMetadata().GetLicenseUrl()) + if helper.Claim.GetStream().GetLicenseUrl() != pair.Claim.LicenseURL { + t.Error("LicenseURL mismatch: expected", pair.Claim.LicenseURL, "got", helper.Claim.GetStream().GetLicenseUrl()) } - if helper.Claim.GetStream().GetMetadata().GetFee().GetAmount() != pair.Claim.FeeAmount { - t.Error("Fee Amount mismatch: expected", pair.Claim.FeeAmount, "got", helper.Claim.GetStream().GetMetadata().GetFee().GetAmount()) + if helper.Claim.GetStream().GetFee().GetAmount() != uint64(pair.Claim.FeeAmount*100000000) { + t.Error("Fee Amount mismatch: expected", pair.Claim.FeeAmount, "got", helper.Claim.GetStream().GetFee().GetAmount()) } - if helper.Claim.GetStream().GetMetadata().GetFee().GetCurrency().String() != pair.Claim.FeeCurrency { - t.Error("Fee Currency mismatch: expected", pair.Claim.FeeCurrency, "got", helper.Claim.GetStream().GetMetadata().GetFee().GetCurrency()) + if helper.Claim.GetStream().GetFee().GetCurrency().String() != pair.Claim.FeeCurrency { + t.Error("Fee Currency mismatch: expected", pair.Claim.FeeCurrency, "got", helper.Claim.GetStream().GetFee().GetCurrency()) } - hexaddress := base58.Encode(helper.Claim.GetStream().GetMetadata().GetFee().GetAddress()) + hexaddress := base58.Encode(helper.Claim.GetStream().GetFee().GetAddress()) if hexaddress != pair.Claim.FeeAddress { t.Error("Fee Address mismatch: expected", pair.Claim.FeeAddress, "got", hexaddress) } - if helper.Claim.GetStream().GetSource().GetContentType() != pair.Claim.ContentType { - t.Error("ContentType mismatch: expected", pair.Claim.ContentType, "got", helper.Claim.GetStream().GetSource().GetContentType()) + if helper.Claim.GetStream().GetMediaType() != pair.Claim.ContentType { + t.Error("ContentType mismatch: expected", pair.Claim.ContentType, "got", helper.Claim.GetStream().GetMediaType()) } - if helper.Claim.GetStream().GetMetadata().GetLanguage().String() != pair.Claim.Language { - t.Error("Language mismatch: expected ", pair.Claim.Language, " got ", helper.Claim.GetStream().GetMetadata().GetLanguage().String()) + if helper.Claim.GetStream().GetLanguages()[0].String() != pair.Claim.Language { + t.Error("Language mismatch: expected ", pair.Claim.Language, " got ", helper.Claim.GetStream().GetLanguages()[0].String()) } - content := hex.EncodeToString(helper.Claim.GetStream().GetSource().GetSource()) + content := hex.EncodeToString(helper.Claim.GetStream().GetSdHash()) if content != pair.Claim.LbrySDHash { t.Error("Source mismatch: expected", pair.Claim.LbrySDHash, "got", content) } } } + +func TestMigrationFromV1YTSync(t *testing.T) { + claimHex := "080110011aee04080112a604080410011a2b4865726520617265203520526561736f6e73204920e29da4efb88f204e657874636c6f7564207c20544c4722920346696e64206f7574206d6f72652061626f7574204e657874636c6f75643a2068747470733a2f2f6e657874636c6f75642e636f6d2f0a0a596f752063616e2066696e64206d65206f6e20746865736520736f6369616c733a0a202a20466f72756d733a2068747470733a2f2f666f72756d2e6865617679656c656d656e742e696f2f0a202a20506f64636173743a2068747470733a2f2f6f6666746f706963616c2e6e65740a202a2050617472656f6e3a2068747470733a2f2f70617472656f6e2e636f6d2f7468656c696e757867616d65720a202a204d657263683a2068747470733a2f2f746565737072696e672e636f6d2f73746f7265732f6f6666696369616c2d6c696e75782d67616d65720a202a205477697463683a2068747470733a2f2f7477697463682e74762f786f6e64616b0a202a20547769747465723a2068747470733a2f2f747769747465722e636f6d2f7468656c696e757867616d65720a0a2e2e2e0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d4672546442434f535f66632a0f546865204c696e75782047616d6572321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f4672546442434f535f666352005a001a41080110011a30040e8ac6e89c061f982528c23ad33829fd7146435bf7a4cc22f0bff70c4fe0b91fd36da9a375e3e1c171db825bf5d1f32209766964656f2f6d70342a5c080110031a4062b2dd4c45e364030fbfad1a6fefff695ebf20ea33a5381b947753e2a0ca359989a5cc7d15e5392a0d354c0b68498382b2701b22c03beb8dcb91089031b871e72214feb61536c007cdf4faeeaab4876cb397feaf6b51" + claim, err := DecodeClaimHex(claimHex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + assert.Assert(t, claim.GetStream().GetTitle() == "Here are 5 Reasons I ❤️ Nextcloud | TLG") + assert.Assert(t, claim.GetStream().GetDescription() == "Find out more about Nextcloud: https://nextcloud.com/\n\nYou can find me on these socials:\n * Forums: https://forum.heavyelement.io/\n * Podcast: https://offtopical.net\n * Patreon: https://patreon.com/thelinuxgamer\n * Merch: https://teespring.com/stores/official-linux-gamer\n * Twitch: https://twitch.tv/xondak\n * Twitter: https://twitter.com/thelinuxgamer\n\n...\nhttps://www.youtube.com/watch?v=FrTdBCOS_fc") + assert.Assert(t, claim.GetStream().GetLicense() == "Copyrighted (contact author)") + assert.Assert(t, claim.GetStream().GetAuthor() == "The Linux Gamer") + //?assert.Assert(t, claim.GetStream().GetLanguages()[0]) + assert.Assert(t, claim.GetStream().GetMediaType() == "video/mp4") + assert.Assert(t, claim.GetStream().GetThumbnailUrl() == "https://berk.ninja/thumbnails/FrTdBCOS_fc") + sdHashBytes, err := hex.DecodeString("040e8ac6e89c061f982528c23ad33829fd7146435bf7a4cc22f0bff70c4fe0b91fd36da9a375e3e1c171db825bf5d1f3") + if err != nil { + t.Error(err) + } + assert.Assert(t, bytes.Equal(claim.GetStream().GetSdHash(), sdHashBytes)) + + channelHex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a034200043878b1edd4a1373149909ef03f4339f6da9c2bd2214c040fd2e530463ffe66098eca14fc70b50ff3aefd106049a815f595ed5a13eda7419ad78d9ed7ae473f17" + channel, err := DecodeClaimHex(channelHex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + pubKeyBytes, err := hex.DecodeString("3056301006072a8648ce3d020106052b8104000a034200043878b1edd4a1373149909ef03f4339f6da9c2bd2214c040fd2e530463ffe66098eca14fc70b50ff3aefd106049a815f595ed5a13eda7419ad78d9ed7ae473f17") + if err != nil { + t.Error(err) + } + assert.Assert(t, bytes.Equal(pubKeyBytes, channel.GetChannel().GetPublicKey())) +} + +func TestMigrationFromV1UnsignedWithFee(t *testing.T) { + claimHex := "080110011ad6010801127c080410011a08727067206d69646922046d6964692a08727067206d696469322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a19553f00bc139bbf40de425f94d51fffb34c1bea6d9171cd374c25000070414a0052005a001a54080110011a301f41eb0312aa7e8a5ce49349bc77d811da975833719d751523b19f123fc3d528d6a94e3446ccddb7b9329f27a9cad7e3221c6170706c69636174696f6e2f782d7a69702d636f6d70726573736564" + claim, err := DecodeClaimHex(claimHex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + assert.Assert(t, claim.GetStream().GetTitle() == "rpg midi") + assert.Assert(t, claim.GetStream().GetDescription() == "midi") + assert.Assert(t, claim.GetStream().GetLicense() == "Creative Commons Attribution 4.0 International") + assert.Assert(t, claim.GetStream().GetAuthor() == "rpg midi") + //assert.Assert(t, claim.GetStream().GetLanguage() == "en") + assert.Assert(t, claim.GetStream().GetMediaType() == "application/x-zip-compressed") + sdHashBytes, err := hex.DecodeString("1f41eb0312aa7e8a5ce49349bc77d811da975833719d751523b19f123fc3d528d6a94e3446ccddb7b9329f27a9cad7e3") + if err != nil { + t.Error(err) + } + assert.Assert(t, bytes.Equal(claim.GetStream().GetSdHash(), sdHashBytes)) + feeAddressBytes, err := address.DecodeAddress("bJUQ9MxS9N6M29zsA5GTpVSDzsnPjMBBX9", "lbrycrd_main") + assert.Assert(t, bytes.Equal(claim.GetStream().GetFee().GetAddress(), feeAddressBytes[:])) + assert.Assert(t, claim.GetStream().GetFee().GetAmount() == 1500000000) + assert.Assert(t, claim.GetStream().GetFee().GetCurrency().String() == "LBC") + +} diff --git a/claim/schema.go b/claim/schema.go index eb3f2ea..ef4b374 100644 --- a/claim/schema.go +++ b/claim/schema.go @@ -3,7 +3,7 @@ package claim import ( "encoding/json" - "github.com/lbryio/lbry.go/errors" + "github.com/lbryio/lbry.go/extras/errors" ) // V1Claim is the first version of claim metadata used by lbry. diff --git a/claim/serialization.go b/claim/serialization.go index f65d7cb..ce85d2f 100644 --- a/claim/serialization.go +++ b/claim/serialization.go @@ -2,43 +2,50 @@ package claim import ( "encoding/hex" - "errors" + + "github.com/lbryio/lbry.go/extras/errors" + legacy "github.com/lbryio/types/v1/go" + pb "github.com/lbryio/types/v2/go" "github.com/golang/protobuf/proto" - "github.com/lbryio/types/go" ) -func (c *ClaimHelper) Serialized() ([]byte, error) { +func (c *ClaimHelper) serialized() ([]byte, error) { serialized := c.String() if serialized == "" { - return nil, errors.New("not initialized") + return nil, errors.Err("not initialized") } - v := c.GetVersion() - t := c.GetClaimType() - return proto.Marshal( - &pb.Claim{ - Version: &v, - ClaimType: &t, - Stream: c.GetStream(), - Certificate: c.GetCertificate(), - PublisherSignature: c.GetPublisherSignature()}) + if c.LegacyClaim != nil { + return proto.Marshal(c.getLegacyProtobuf()) + } + + return proto.Marshal(c.getProtobuf()) } -func (c *ClaimHelper) GetProtobuf() *pb.Claim { - v := c.GetVersion() - t := c.GetClaimType() +func (c *ClaimHelper) getProtobuf() *pb.Claim { + if c.GetChannel() != nil { + return &pb.Claim{Type: &pb.Claim_Channel{Channel: c.GetChannel()}} + } else if c.GetStream() != nil { + return &pb.Claim{Type: &pb.Claim_Stream{Stream: c.GetStream()}} + } - return &pb.Claim{ + return nil +} + +func (c *ClaimHelper) getLegacyProtobuf() *legacy.Claim { + v := c.LegacyClaim.GetVersion() + t := c.LegacyClaim.GetClaimType() + return &legacy.Claim{ Version: &v, ClaimType: &t, - Stream: c.GetStream(), - Certificate: c.GetCertificate(), - PublisherSignature: c.GetPublisherSignature()} + Stream: c.LegacyClaim.GetStream(), + Certificate: c.LegacyClaim.GetCertificate(), + PublisherSignature: c.LegacyClaim.GetPublisherSignature()} } -func (c *ClaimHelper) SerializedHexString() (string, error) { - serialized, err := c.Serialized() +func (c *ClaimHelper) serializedHexString() (string, error) { + serialized, err := c.serialized() if err != nil { return "", err } @@ -46,21 +53,26 @@ func (c *ClaimHelper) SerializedHexString() (string, error) { return serialized_hex, nil } -func (c *ClaimHelper) SerializedNoSignature() ([]byte, error) { +func (c *ClaimHelper) serializedNoSignature() ([]byte, error) { if c.String() == "" { - return nil, errors.New("not initialized") + return nil, errors.Err("not initialized") } - if c.GetPublisherSignature() == nil { - serialized, err := c.Serialized() + if c.Signature == nil { + serialized, err := c.serialized() if err != nil { return nil, err } return serialized, nil } else { + if c.LegacyClaim != nil { + clone := &legacy.Claim{} + proto.Merge(clone, c.getLegacyProtobuf()) + proto.ClearAllExtensions(clone.PublisherSignature) + clone.PublisherSignature = nil + return proto.Marshal(clone) + } clone := &pb.Claim{} - proto.Merge(clone, c.GetProtobuf()) - proto.ClearAllExtensions(clone.PublisherSignature) - clone.PublisherSignature = nil + proto.Merge(clone, c.getProtobuf()) return proto.Marshal(clone) } } diff --git a/claim/sign.go b/claim/sign.go new file mode 100644 index 0000000..18a2a2f --- /dev/null +++ b/claim/sign.go @@ -0,0 +1,112 @@ +package claim + +import ( + "crypto/sha256" + "encoding/hex" + + "github.com/lbryio/lbry.go/extras/errors" + "github.com/lbryio/lbryschema.go/address" + + "github.com/btcsuite/btcd/btcec" +) + +func Sign(privKey btcec.PrivateKey, channel ClaimHelper, claim ClaimHelper, k string) (*Signature, error) { + if channel.GetChannel() == nil { + return nil, errors.Err("claim as channel is not of type channel") + } + if claim.LegacyClaim != nil { + return claim.signV1(privKey, channel, k) + } + + return claim.sign(privKey, channel, k) +} + +func (c *ClaimHelper) sign(privKey btcec.PrivateKey, channel ClaimHelper, firstInputTxID string) (*Signature, error) { + + txidBytes, err := hex.DecodeString(firstInputTxID) + if err != nil { + return nil, errors.Err(err) + } + + metadataBytes, err := c.serialized() + if err != nil { + return nil, errors.Err(err) + } + + var digest []byte + digest = append(digest, txidBytes...) + digest = append(digest, c.ClaimID...) + digest = append(digest, metadataBytes...) + hash := sha256.Sum256(digest) + hashBytes := make([]byte, len(hash)) + for i, b := range hash { + hashBytes[i] = b + } + + sig, err := privKey.Sign(hashBytes) + if err != nil { + return nil, errors.Err(err) + } + + return &Signature{*sig}, nil + +} + +func (c *ClaimHelper) signV1(privKey btcec.PrivateKey, channel ClaimHelper, claimAddress string) (*Signature, error) { + metadataBytes, err := c.serializedNoSignature() + if err != nil { + return nil, errors.Err(err) + } + + addressBytes, err := address.DecodeAddress(claimAddress, "lbrycrd_main") + if err != nil { + return nil, errors.Prefix("V1 signing requires claim address and the decode failed with: ", err) + } + + var digest []byte + + address := make([]byte, len(addressBytes)) + for i, b := range addressBytes { + address[i] = b + } + + digest = append(digest, address...) + digest = append(digest, metadataBytes...) + digest = append(digest, channel.ClaimID...) + + hash := sha256.Sum256(digest) + hashBytes := make([]byte, len(hash)) + for i, b := range hash { + hashBytes[i] = b + } + + sig, err := privKey.Sign(hashBytes) + if err != nil { + return nil, errors.Err(err) + } + + return &Signature{*sig}, nil +} + +type Signature struct { + btcec.Signature +} + +func (s *Signature) LBRYSDKEncode() ([]byte, error) { + if s.R == nil || s.S == nil { + return nil, errors.Err("invalid signature, both S & R are nil") + } + rBytes := s.R.Bytes() + sBytes := s.S.Bytes() + + return append(rBytes, sBytes...), nil +} + +// rev reverses a byte slice. useful for switching endian-ness +func reverseBytes(b []byte) []byte { + r := make([]byte, len(b)) + for left, right := 0, len(b)-1; left < right; left, right = left+1, right-1 { + r[left], r[right] = b[right], b[left] + } + return r +} diff --git a/claim/sign_test.go b/claim/sign_test.go new file mode 100644 index 0000000..3f6a7ff --- /dev/null +++ b/claim/sign_test.go @@ -0,0 +1,137 @@ +package claim + +import ( + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcec" + "gotest.tools/assert" +) + +func TestSign(t *testing.T) { + privateKey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Error(err) + return + } + channel := &ClaimHelper{newChannelClaim(), nil, nil, NoSig, nil} + pubkeyBytes, err := PublicKeyToDER(privateKey.PubKey()) + if err != nil { + t.Error(err) + return + } + channel.GetChannel().PublicKey = pubkeyBytes + claimID := "cf3f7c898af87cc69b06a6ac7899efb9a4878fdb" //Fake + txid := "4c1df9e022e396859175f9bfa69b38e444db10fb53355fa99a0989a83bcdb82f" //Fake + claimIDHexBytes, err := hex.DecodeString(claimID) + if err != nil { + t.Error(err) + return + } + + claim := &ClaimHelper{newStreamClaim(), nil, reverseBytes(claimIDHexBytes), WithSig, nil} + claim.Claim.GetStream().Title = "Test title" + claim.Claim.GetStream().Description = "Test description" + sig, err := Sign(*privateKey, *channel, *claim, txid) + if err != nil { + t.Error(err) + return + } + + signatureBytes, err := sig.LBRYSDKEncode() + if err != nil { + t.Error(err) + return + } + + claim.Signature = signatureBytes + + rawChannel, err := channel.CompileValue() + if err != nil { + t.Error(err) + return + } + rawClaim, err := claim.CompileValue() + if err != nil { + t.Error(err) + return + } + + channel, err = DecodeClaimBytes(rawChannel, "lbrycrd_main") + if err != nil { + t.Error(err) + return + } + claim, err = DecodeClaimBytes(rawClaim, "lbrycrd_main") + if err != nil { + t.Error(err) + return + } + + valid, err := claim.ValidateClaimSignature(channel, txid, claimID, "lbrycrd_main") + if err != nil { + t.Error(err) + return + } + + assert.Assert(t, valid, "could not verify signature") + +} + +func TestSignWithV1Channel(t *testing.T) { + cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367" + channel, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + privateKey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Error(err) + return + } + pubkeyBytes, err := PublicKeyToDER(privateKey.PubKey()) + if err != nil { + t.Error(err) + return + } + channel.GetChannel().PublicKey = pubkeyBytes + + claimID := "251305ca93d4dbedb50dceb282ebcb7b07b7ac64" + txid := "4c1df9e022e396859175f9bfa69b38e444db10fb53355fa99a0989a83bcdb82f" //Fake + claimIDHexBytes, err := hex.DecodeString(claimID) + if err != nil { + t.Error(err) + return + } + + claim := &ClaimHelper{newStreamClaim(), nil, reverseBytes(claimIDHexBytes), WithSig, nil} + claim.Claim.GetStream().Title = "Test title" + claim.Claim.GetStream().Description = "Test description" + sig, err := Sign(*privateKey, *channel, *claim, txid) + if err != nil { + t.Error(err) + return + } + + signatureBytes, err := sig.LBRYSDKEncode() + if err != nil { + t.Error(err) + return + } + + claim.Signature = signatureBytes + compiledClaim, err := claim.CompileValue() + if err != nil { + t.Error(err) + } + claim, err = DecodeClaimBytes(compiledClaim, "lbrycrd_main") + + valid, err := claim.ValidateClaimSignature(channel, txid, claimID, "lbrycrd_main") + if err != nil { + t.Error(err) + return + } + + assert.Assert(t, valid, "could not verify signature") + +} diff --git a/claim/validator.go b/claim/validator.go index 40ee868..4edac28 100644 --- a/claim/validator.go +++ b/claim/validator.go @@ -6,10 +6,10 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/hex" - "errors" - "github.com/btcsuite/btcd/btcec" - "github.com/lbryio/lbryschema.go/address" "math/big" + + "github.com/lbryio/lbry.go/extras/errors" + "github.com/lbryio/lbryschema.go/address" ) type publicKeyInfo struct { @@ -23,86 +23,100 @@ const SECP256k1 = "SECP256k1" //const NIST256p = "NIST256p" //const NIST384p = "NIST384p" -func GetClaimSignatureDigest(claimAddress [25]byte, certificateId [20]byte, serializedNoSig []byte) [32]byte { +func getClaimSignatureDigest(bytes ...[]byte) [32]byte { + var combined []byte - for _, c := range claimAddress { - combined = append(combined, c) - } - for _, c := range serializedNoSig { - combined = append(combined, c) - } - for _, c := range certificateId { - combined = append(combined, c) + for _, b := range bytes { + combined = append(combined, b...) } digest := sha256.Sum256(combined) return [32]byte(digest) } -func (c *ClaimHelper) GetCertificatePublicKey() (*btcec.PublicKey, error) { - derBytes := c.GetCertificate().GetPublicKey() - pub := publicKeyInfo{} - asn1.Unmarshal(derBytes, &pub) - pubkeyBytes := []byte(pub.PublicKey.Bytes) - p, err := btcec.ParsePubKey(pubkeyBytes, btcec.S256()) - if err != nil { - return &btcec.PublicKey{}, err - } - return p, err -} - func (c *ClaimHelper) VerifyDigest(certificate *ClaimHelper, signature [64]byte, digest [32]byte) bool { - publicKey, err := certificate.GetCertificatePublicKey() - if err != nil { + if certificate == nil { return false } - if c.PublisherSignature.SignatureType.String() == SECP256k1 { - R := &big.Int{} - S := &big.Int{} - R.SetBytes(signature[0:32]) - S.SetBytes(signature[32:64]) - return ecdsa.Verify(publicKey.ToECDSA(), digest[:], R, S) + R := &big.Int{} + S := &big.Int{} + R.SetBytes(signature[0:32]) + S.SetBytes(signature[32:64]) + pk, err := certificate.GetPublicKey() + if err != nil { + return false } - return false + return ecdsa.Verify(pk.ToECDSA(), digest[:], R, S) } -func (c *ClaimHelper) ValidateClaimSignatureBytes(certificate *ClaimHelper, claimAddress [25]byte, certificateId [20]byte, blockchainName string) (bool, error) { - signature := c.GetPublisherSignature() +func (c *ClaimHelper) ValidateClaimSignature(certificate *ClaimHelper, k string, certificateId string, blockchainName string) (bool, error) { + if c.LegacyClaim != nil { + return c.validateV1ClaimSignature(certificate, k, certificateId, blockchainName) + } + + return c.validateClaimSignature(certificate, k, certificateId, blockchainName) +} + +func (c *ClaimHelper) validateClaimSignature(certificate *ClaimHelper, firstInputTxID, certificateId string, blockchainName string) (bool, error) { + certificateIdSlice, err := hex.DecodeString(certificateId) + if err != nil { + return false, errors.Err(err) + } + certificateIdSlice = reverseBytes(certificateIdSlice) + firstInputTxIDBytes, err := hex.DecodeString(firstInputTxID) + if err != nil { + return false, errors.Err(err) + } + + signature := c.Signature if signature == nil { - return false, errors.New("claim does not have a signature") + return false, errors.Err("claim does not have a signature") } - signatureSlice := signature.GetSignature() signatureBytes := [64]byte{} - for i := range signatureBytes { - signatureBytes[i] = signatureSlice[i] + for i, b := range signature { + signatureBytes[i] = b } - claimAddress, err := address.ValidateAddress(claimAddress, blockchainName) + serialized, err := c.serialized() if err != nil { - return false, errors.New("invalid address") + return false, errors.Err("serialization error") } - serializedNoSig, err := c.SerializedNoSignature() - if err != nil { - return false, errors.New("serialization error") - } - - claimDigest := GetClaimSignatureDigest(claimAddress, certificateId, serializedNoSig) + claimDigest := getClaimSignatureDigest(firstInputTxIDBytes, certificateIdSlice, serialized) return c.VerifyDigest(certificate, signatureBytes, claimDigest), nil } -func (c *ClaimHelper) ValidateClaimSignature(certificate *ClaimHelper, claimAddress string, certificateId string, blockchainName string) (bool, error) { - addressBytes, err := address.DecodeAddress(claimAddress, blockchainName) +func (c *ClaimHelper) validateV1ClaimSignature(certificate *ClaimHelper, claimAddy string, certificateId string, blockchainName string) (bool, error) { + addressBytes, err := address.DecodeAddress(claimAddy, blockchainName) if err != nil { return false, err } + //For V1 claim_id was incorrectly stored for claim signing. + // So the bytes are not reversed like they are supposed to be (Endianess) certificateIdSlice, err := hex.DecodeString(certificateId) if err != nil { return false, err } - certificateIdBytes := [20]byte{} - for i := range certificateIdBytes { - certificateIdBytes[i] = certificateIdSlice[i] + + signature := c.Signature + if signature == nil { + return false, errors.Err("claim does not have a signature") } - return c.ValidateClaimSignatureBytes(certificate, addressBytes, certificateIdBytes, blockchainName) + signatureBytes := [64]byte{} + for i := range signatureBytes { + signatureBytes[i] = signature[i] + } + + claimAddress, err := address.ValidateAddress(addressBytes, blockchainName) + if err != nil { + return false, errors.Err("invalid address") + } + + serializedNoSig, err := c.serializedNoSignature() + if err != nil { + return false, errors.Err("serialization error") + } + + claimDigest := getClaimSignatureDigest(claimAddress[:], serializedNoSig, certificateIdSlice) + return c.VerifyDigest(certificate, signatureBytes, claimDigest), nil } diff --git a/claim/validator_test.go b/claim/validator_test.go index 58b68d4..eca3ba6 100644 --- a/claim/validator_test.go +++ b/claim/validator_test.go @@ -4,12 +4,18 @@ import ( "testing" ) -func TestValidateClaimSignature(t *testing.T) { +func TestV1ValidateClaimSignature(t *testing.T) { cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367" signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65" - signed_claim, _ := DecodeClaimHex(signed_claim_hex, "lbrycrd_main") - cert_claim, _ := DecodeClaimHex(cert_claim_hex, "lbrycrd_main") + signed_claim, err := DecodeClaimHex(signed_claim_hex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + cert_claim, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main") + if err != nil { + t.Error(err) + } claim_addr := "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt" cert_id := "251305ca93d4dbedb50dceb282ebcb7b07b7ac65" @@ -23,12 +29,18 @@ func TestValidateClaimSignature(t *testing.T) { } } -func TestFailToValidateClaimSignature(t *testing.T) { +func TestV1FailToValidateClaimSignature(t *testing.T) { cert_claim_hex := "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367" signed_claim_hex := "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65" - signed_claim, _ := DecodeClaimHex(signed_claim_hex, "lbrycrd_main") - cert_claim, _ := DecodeClaimHex(cert_claim_hex, "lbrycrd_main") + signed_claim, err := DecodeClaimHex(signed_claim_hex, "lbrycrd_main") + if err != nil { + t.Error(err) + } + cert_claim, err := DecodeClaimHex(cert_claim_hex, "lbrycrd_main") + if err != nil { + t.Error(err) + } claim_addr := "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt" cert_id := "251305ca93d4dbedb50dceb282ebcb7b07b7ac64" diff --git a/test_binding.py b/test_binding.py deleted file mode 100644 index ae6a08f..0000000 --- a/test_binding.py +++ /dev/null @@ -1,91 +0,0 @@ -from ctypes import * - - -class _GoString(Structure): - _fields_ = [ - ("p", c_char_p), - ("n", c_longlong) - ] - - -def GoString(s): - if not isinstance(s, (str, unicode)): - raise TypeError("invalid type: %s" % str(type(s))) - x = str(s) - return _GoString(x, len(x)) - - -def GoBinding(library, *argTypes): - """ - Get a binding to a go function of the same name in the given .so - The python function itself is not run, and can return or pass - - :param argTypes: *parameter types - """ - - lib = cdll.LoadLibrary(library) - - def inner(fn): - _types = { - str: _GoString - } - - _getters = { - str: GoString - } - - for v in argTypes: - if not isinstance(v, type): - raise TypeError("invalid argument type") - if v not in _types: - raise TypeError("type does not have a mapping: %s" % str(type(v))) - - _go_func = getattr(lib, fn.__name__) - _go_func.argtypes = [_types[v] for v in argTypes] - - def _wrap(*args): - _args = [_getters[arg_type](arg) for arg_type, arg in zip(argTypes, args)] - return _go_func(*tuple(_args)) - return _wrap - return inner - - -@GoBinding("./lbryschema-python-binding.so", str, str, str, str) -def VerifySignature(claim, certificate, claim_address, certificate_id): - pass - - -cert_claim_hex = "08011002225e0801100322583056301006072a8648ce3d020106052b8104000a03420004d015365a40f3e5c03c87227168e5851f44659837bcf6a3398ae633bc37d04ee19baeb26dc888003bd728146dbea39f5344bf8c52cedaf1a3a1623a0166f4a367" -signed_claim_hex = "080110011ad7010801128f01080410011a0c47616d65206f66206c696665221047616d65206f66206c696665206769662a0b4a6f686e20436f6e776179322e437265617469766520436f6d6d6f6e73204174747269627574696f6e20342e3020496e7465726e6174696f6e616c38004224080110011a195569c917f18bf5d2d67f1346aa467b218ba90cdbf2795676da250000803f4a0052005a001a41080110011a30b6adf6e2a62950407ea9fb045a96127b67d39088678d2f738c359894c88d95698075ee6203533d3c204330713aa7acaf2209696d6167652f6769662a5c080110031a40c73fe1be4f1743c2996102eec6ce0509e03744ab940c97d19ddb3b25596206367ab1a3d2583b16c04d2717eeb983ae8f84fee2a46621ffa5c4726b30174c6ff82214251305ca93d4dbedb50dceb282ebcb7b07b7ac65" -claim_addr = "bSkUov7HMWpYBiXackDwRnR5ishhGHvtJt" -cert_id = "251305ca93d4dbedb50dceb282ebcb7b07b7ac65" -import time -from lbryschema.decode import smart_decode - -cd = smart_decode(signed_claim_hex) -certd = smart_decode(cert_claim_hex) - - -def clock_lbryschema_python(n=10.0): - start = time.time() - for i in range(int(n)): - assert cd.validate_signature(claim_addr, certd) - if i % 10 == 0: - print i - avg = float(time.time() - start) / n - return 1.0 / avg - - -def clock_lbryschema_go(n=100.0): - start = time.time() - for i in range(int(n)): - assert VerifySignature(signed_claim_hex, cert_claim_hex, claim_addr, cert_id) - if i % 10 == 0: - print i - avg = float(time.time() - start) / n - return 1.0 / avg - -print "Start" -print "%f validations / second with python" % clock_lbryschema_python() -print "%f validations / second with go binding" % clock_lbryschema_go() - -- 2.45.2