added support for proto3 metadata definitions

added signing capabilities
all with unit tests
This commit is contained in:
Mark Beamer Jr 2019-04-07 00:29:12 -04:00
parent 50996a6b0d
commit 9159c7602d
No known key found for this signature in database
GPG key ID: 1C314FB89AD76973
18 changed files with 842 additions and 427 deletions

View file

@ -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 {

View file

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

View file

@ -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() {}

View file

@ -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

View file

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

View file

@ -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)
}

View file

@ -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")
}
}

50
claim/keys.go Normal file
View file

@ -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())
}

36
claim/keys_test.go Normal file
View file

@ -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!")
}

View file

@ -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 = &currency
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)
}
}

View file

@ -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")
}

View file

@ -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.

View file

@ -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)
}
}

112
claim/sign.go Normal file
View file

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

137
claim/sign_test.go Normal file
View file

@ -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")
}

View file

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

View file

@ -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"

View file

@ -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()