fabtoken

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 16, 2022 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0

Index

Constants

View Source
const (
	// PublicParameters is the key to be used to look up fabtoken parameters
	PublicParameters = "fabtoken"
	DefaultPrecision = uint64(64)
)

Variables

This section is empty.

Functions

func NewDeserializer

func NewDeserializer() *deserializer

NewDeserializer returns a deserializer

func NewEnrollmentIDDeserializer

func NewEnrollmentIDDeserializer() *enrollmentService

NewEnrollmentIDDeserializer returns an enrollmentService

func RetrieveInputsFromTransferAction

func RetrieveInputsFromTransferAction(t *TransferAction, ledger driver.Ledger) ([]*token2.Token, error)

RetrieveInputsFromTransferAction retrieves from the passed ledger the inputs identified in TransferAction

func TransferBalanceValidate

func TransferBalanceValidate(ctx *Context) error

TransferBalanceValidate checks that the sum of the inputs is equal to the sum of the outputs

func TransferHTLCValidate

func TransferHTLCValidate(ctx *Context) error

TransferHTLCValidate checks the validity of the HTLC scripts, if any

func TransferSignatureValidate

func TransferSignatureValidate(ctx *Context) error

TransferSignatureValidate validates the signatures for the inputs spent by an action

func UnmarshalIssueTransferActions

func UnmarshalIssueTransferActions(tr *driver.TokenRequest) ([]*IssueAction, []*TransferAction, error)

UnmarshalIssueTransferActions returns the deserialized issue and transfer actions contained in the passed TokenRequest

Types

type Context

type Context struct {
	PP                *PublicParams
	Deserializer      driver.Deserializer
	SignatureProvider driver.SignatureProvider
	Signatures        [][]byte
	InputTokens       []*token.Token
	Action            *TransferAction
	Ledger            driver.Ledger
	MetadataCounter   map[string]int
}

func (*Context) CountMetadataKey

func (c *Context) CountMetadataKey(key string)

type IssueAction

type IssueAction struct {
	// issuer's public key
	Issuer view.Identity
	// new tokens to be issued
	Outputs []*Output
	// metadata of the issue action
	Metadata map[string][]byte
}

IssueAction encodes a fabtoken Issue

func UnmarshalIssueActions

func UnmarshalIssueActions(raw [][]byte) ([]*IssueAction, error)

UnmarshalIssueActions returns an array of deserialized IssueAction from raw bytes

func (*IssueAction) Deserialize

func (i *IssueAction) Deserialize(raw []byte) error

Deserialize un-marshals IssueAction

func (*IssueAction) GetIssuer

func (i *IssueAction) GetIssuer() []byte

GetIssuer returns the issuer encoded in IssueAction

func (*IssueAction) GetMetadata

func (i *IssueAction) GetMetadata() map[string][]byte

GetMetadata returns the IssueAction metadata

func (*IssueAction) GetOutputs

func (i *IssueAction) GetOutputs() []driver.Output

GetOutputs returns the outputs in an IssueAction

func (*IssueAction) GetSerializedOutputs

func (i *IssueAction) GetSerializedOutputs() ([][]byte, error)

GetSerializedOutputs returns the serialization of the outputs in an IssueAction

func (*IssueAction) IsAnonymous

func (i *IssueAction) IsAnonymous() bool

IsAnonymous returns false, indicating that the identity of issuers in fabtoken is revealed during issue

func (*IssueAction) NumOutputs

func (i *IssueAction) NumOutputs() int

NumOutputs returns the number of outputs in an IssueAction

func (*IssueAction) Serialize

func (i *IssueAction) Serialize() ([]byte, error)

Serialize marshals IssueAction

type KVS

type KVS interface {
	Exists(id string) bool
	Put(id string, state interface{}) error
	Get(id string, state interface{}) error
}

type Output

type Output struct {
	Output *token.Token
}

Output carries the output of an action

func (*Output) IsRedeem

func (t *Output) IsRedeem() bool

IsRedeem returns true if the owner of a Output is empty todo update interface to account for nil t.Output.Owner and nil t.Output

func (*Output) Serialize

func (t *Output) Serialize() ([]byte, error)

Serialize marshals a Output

type OutputMetadata

type OutputMetadata struct {
	Issuer []byte
}

OutputMetadata contains a serialization of the issuer of the token. type, value and owner of token can be derived from the token itself.

func (*OutputMetadata) Deserialize

func (m *OutputMetadata) Deserialize(b []byte) error

Deserialize un-marshals OutputMetadata

func (*OutputMetadata) Serialize

func (m *OutputMetadata) Serialize() ([]byte, error)

Serialize marshals OutputMetadata

type PublicParametersManager

type PublicParametersManager interface {
	driver.PublicParamsManager
	PublicParams() *PublicParams
}

type PublicParams

type PublicParams struct {
	// Label is the label associated with the PublicParams.
	// It can be used by the driver for versioning purpose.
	Label string
	// The precision of token quantities
	QuantityPrecision uint64
	// This is set when audit is enabled
	Auditor []byte
	// This encodes the list of authorized issuers
	Issuers [][]byte
	// MaxToken is the maximum quantity a token can hold
	MaxToken uint64
}

PublicParams is the public parameters for fabtoken

func NewPublicParamsFromBytes

func NewPublicParamsFromBytes(raw []byte, label string) (*PublicParams, error)

NewPublicParamsFromBytes deserializes the raw bytes into public parameters The resulting public parameters are labeled with the passed label

func Setup

func Setup() (*PublicParams, error)

Setup initializes PublicParams

func (*PublicParams) AddAuditor

func (pp *PublicParams) AddAuditor(auditor view.Identity)

AddAuditor sets the Auditor field in PublicParams to the passed identity

func (*PublicParams) AddIssuer

func (pp *PublicParams) AddIssuer(issuer view.Identity)

AddIssuer adds the passed issuer to the array of Issuers in PublicParams

func (*PublicParams) AuditorIdentity

func (pp *PublicParams) AuditorIdentity() view.Identity

AuditorIdentity returns the auditor identity encoded in PublicParams

func (*PublicParams) Auditors

func (pp *PublicParams) Auditors() []view.Identity

Auditors returns the list of authorized auditors fabtoken only supports a single auditor

func (*PublicParams) Bytes

func (pp *PublicParams) Bytes() ([]byte, error)

Bytes marshals PublicParams

func (*PublicParams) CertificationDriver

func (pp *PublicParams) CertificationDriver() string

CertificationDriver returns the label of the PublicParams From the label, one can deduce what certification process will be used if any.

func (*PublicParams) ComputeMaxTokenValue

func (pp *PublicParams) ComputeMaxTokenValue() uint64

func (*PublicParams) Deserialize

func (pp *PublicParams) Deserialize(raw []byte) error

Deserialize un-marshals the passed bytes into PublicParams

func (*PublicParams) GraphHiding

func (pp *PublicParams) GraphHiding() bool

GraphHiding indicates if the PublicParams corresponds to a driver that hides the transaction graph fabtoken does not hide the graph, hence, GraphHiding returns false

func (*PublicParams) Identifier

func (pp *PublicParams) Identifier() string

Identifier returns the label associated with the PublicParams todo shall we used Identifier instead of Label?

func (*PublicParams) MaxTokenValue

func (pp *PublicParams) MaxTokenValue() uint64

MaxTokenValue returns the maximum value that a token can hold according to PublicParams

func (*PublicParams) Precision

func (pp *PublicParams) Precision() uint64

Precision returns the quantity precision encoded in PublicParams

func (*PublicParams) Serialize

func (pp *PublicParams) Serialize() ([]byte, error)

Serialize marshals a wrapper around PublicParams (SerializedPublicParams)

func (*PublicParams) TokenDataHiding

func (pp *PublicParams) TokenDataHiding() bool

TokenDataHiding indicates if the PublicParams corresponds to a driver that hides token data fabtoken does not hide token data, hence, TokenDataHiding returns false

func (*PublicParams) Validate

func (pp *PublicParams) Validate() error

Validate validates the public parameters

type PublicParamsLoader

type PublicParamsLoader interface {
	// Fetch fetches the public parameters from the backend
	Fetch() ([]byte, error)
	// FetchParams fetches the public parameters from the backend and unmarshal them.
	// The public parameters are also validated.
	FetchParams() (*PublicParams, error)
}

type PublicParamsManager

type PublicParamsManager struct {
	// contains filtered or unexported fields
}

PublicParamsManager loads fabtoken public parameters

func NewPublicParamsManager

func NewPublicParamsManager(publicParamsLoader PublicParamsLoader) *PublicParamsManager

NewPublicParamsManager initializes a PublicParamsManager with the passed PublicParamsLoader

func NewPublicParamsManagerFromParams

func NewPublicParamsManagerFromParams(pp *PublicParams) *PublicParamsManager

NewPublicParamsManagerFromParams initializes a PublicParamsManager with the passed PublicParams

func (*PublicParamsManager) AuditorIdentity

func (v *PublicParamsManager) AuditorIdentity() view.Identity

AuditorIdentity returns the identity of the auditor

func (*PublicParamsManager) Fetch

func (v *PublicParamsManager) Fetch() ([]byte, error)

Fetch fetches the public parameters from the backend

func (*PublicParamsManager) Issuers

func (v *PublicParamsManager) Issuers() [][]byte

Issuers returns the array of admissible issuers

func (*PublicParamsManager) NewCertifierKeyPair

func (v *PublicParamsManager) NewCertifierKeyPair() ([]byte, []byte, error)

NewCertifierKeyPair returns the key pair of a certifier, in this instantiation, the method panics fabtoken does not support token certification

func (*PublicParamsManager) PublicParameters

func (v *PublicParamsManager) PublicParameters() driver.PublicParameters

PublicParameters returns the public parameters of PublicParamsManager

func (*PublicParamsManager) PublicParams

func (v *PublicParamsManager) PublicParams() *PublicParams

PublicParams returns the fabtoken public parameters

func (*PublicParamsManager) SerializePublicParameters

func (v *PublicParamsManager) SerializePublicParameters() ([]byte, error)

SerializePublicParameters returns the public params in a serialized form

func (*PublicParamsManager) Update

func (v *PublicParamsManager) Update() error

Update sets the public parameters of the PublicParamsManager to the public parameters associated with its PublicParamsLoader

func (*PublicParamsManager) Validate

func (v *PublicParamsManager) Validate() error

Validate validates the public parameters

type QueryEngine

type QueryEngine interface {
	IsMine(id *token2.ID) (bool, error)
	// UnspentTokensIteratorBy returns an iterator of unspent tokens owned by the passed id and whose type is the passed on.
	// The token type can be empty. In that case, tokens of any type are returned.
	UnspentTokensIteratorBy(id, typ string) (driver.UnspentTokensIterator, error)
	ListAuditTokens(ids ...*token2.ID) ([]*token2.Token, error)
	ListHistoryIssuedTokens() (*token2.IssuedTokens, error)
	PublicParams() ([]byte, error)
}

type Service

type Service struct {
	SP          view2.ServiceProvider
	TMSID       token.TMSID
	PPM         PublicParametersManager
	TokenLoader TokenLoader
	QE          QueryEngine
	CM          config.Manager

	IP                     driver.IdentityProvider
	Deserializer           driver.Deserializer
	OwnerWalletsRegistry   *identity.WalletsRegistry
	IssuerWalletsRegistry  *identity.WalletsRegistry
	AuditorWalletsRegistry *identity.WalletsRegistry
}

func NewService

func NewService(
	sp view2.ServiceProvider,
	tmsID token.TMSID,
	ppm PublicParametersManager,
	tokenLoader TokenLoader,
	qe QueryEngine,
	identityProvider driver.IdentityProvider,
	deserializer driver.Deserializer,
	cm config.Manager,
	kvs KVS,
) *Service

func (*Service) AuditorCheck

func (s *Service) AuditorCheck(tokenRequest *driver.TokenRequest, tokenRequestMetadata *driver.TokenRequestMetadata, txID string) error

AuditorCheck verifies if the passed tokenRequest matches the tokenRequestMetadata fabtoken does not make use of AuditorCheck as the token request contains token information in the clear

func (*Service) AuditorWallet

func (s *Service) AuditorWallet(id string) driver.AuditorWallet

func (*Service) AuditorWalletByIdentity

func (s *Service) AuditorWalletByIdentity(id view.Identity) driver.AuditorWallet

func (*Service) CertifierWallet

func (s *Service) CertifierWallet(id string) driver.CertifierWallet

func (*Service) CertifierWalletByIdentity

func (s *Service) CertifierWalletByIdentity(id view.Identity) driver.CertifierWallet

func (*Service) Certify

func (s *Service) Certify(wallet driver.CertifierWallet, ids []*token2.ID, tokens [][]byte, request []byte) ([][]byte, error)

Certify returns an array of serialized certifications, such that the i^th certification asserts that the i^th passed token corresponds to the token associated with the i^th passed identifier fabtoken does not make use of the certification service

func (*Service) ConfigManager

func (s *Service) ConfigManager() config.Manager

func (*Service) DeserializeIssueAction

func (s *Service) DeserializeIssueAction(raw []byte) (driver.IssueAction, error)

DeserializeIssueAction un-marshals the passed bytes into an IssueAction If unmarshalling fails, then DeserializeIssueAction returns an error

func (*Service) DeserializeToken

func (s *Service) DeserializeToken(outputRaw []byte, tokenInfoRaw []byte) (*token2.Token, view.Identity, error)

DeserializeToken returns a deserialized token and the identity of its issuer

func (*Service) DeserializeTransferAction

func (s *Service) DeserializeTransferAction(raw []byte) (driver.TransferAction, error)

DeserializeTransferAction un-marshals a TransferAction from the passed array of bytes. DeserializeTransferAction returns an error, if the un-marshalling fails.

func (*Service) GetAuditInfo

func (s *Service) GetAuditInfo(id view.Identity) ([]byte, error)

func (*Service) GetAuditorVerifier

func (s *Service) GetAuditorVerifier(id view.Identity) (driver.Verifier, error)

GetAuditorVerifier deserializes the verifier for the passed auditor identity

func (*Service) GetEnrollmentID

func (s *Service) GetEnrollmentID(auditInfo []byte) (string, error)

func (*Service) GetIssuerVerifier

func (s *Service) GetIssuerVerifier(id view.Identity) (driver.Verifier, error)

GetIssuerVerifier deserializes the verifier for the passed issuer identity

func (*Service) GetOwnerMatcher

func (s *Service) GetOwnerMatcher(raw []byte) (driver.Matcher, error)

GetOwnerMatcher deserializes the passed bytes into a Matcher The Matcher can be used later to match an identity to its audit information

func (*Service) GetOwnerVerifier

func (s *Service) GetOwnerVerifier(id view.Identity) (driver.Verifier, error)

GetOwnerVerifier deserializes the verifier for the passed owner identity

func (*Service) HistoryIssuedTokens

func (s *Service) HistoryIssuedTokens() (*token2.IssuedTokens, error)

HistoryIssuedTokens returns the list of all issued tokens An IssuedToken consists of the identity of the token issuer, the token unique identifier and information

func (*Service) IdentityProvider

func (s *Service) IdentityProvider() driver.IdentityProvider

func (*Service) Issue

func (s *Service) Issue(issuerIdentity view.Identity, typ string, values []uint64, owners [][]byte, opts *driver.IssueOptions) (driver.IssueAction, [][]byte, view.Identity, error)

Issue returns an IssueAction as a function of the passed arguments Issue also returns a serialization OutputMetadata associated with issued tokens and the identity of the issuer

func (*Service) IssuerWallet

func (s *Service) IssuerWallet(id string) driver.IssuerWallet

func (*Service) IssuerWalletByIdentity

func (s *Service) IssuerWalletByIdentity(id view.Identity) driver.IssuerWallet

func (*Service) MarshalTokenRequestToSign

func (s *Service) MarshalTokenRequestToSign(request *driver.TokenRequest, meta *driver.TokenRequestMetadata) ([]byte, error)

func (*Service) NewCertificationRequest

func (s *Service) NewCertificationRequest(ids []*token2.ID) ([]byte, error)

NewCertificationRequest creates a request to certify the tokens identified by the passed identifiers fabtoken does not make use of the certification service

func (*Service) OwnerWallet

func (s *Service) OwnerWallet(walletID string) driver.OwnerWallet

func (*Service) OwnerWalletByID

func (s *Service) OwnerWalletByID(id interface{}) driver.OwnerWallet

func (*Service) OwnerWalletByIdentity

func (s *Service) OwnerWalletByIdentity(identity view.Identity) driver.OwnerWallet

func (*Service) PublicParamsManager

func (s *Service) PublicParamsManager() driver.PublicParamsManager

func (*Service) RegisterAuditInfo

func (s *Service) RegisterAuditInfo(id view.Identity, auditInfo []byte) error

func (*Service) RegisterIssuerWallet

func (s *Service) RegisterIssuerWallet(id string, path string) error

func (*Service) RegisterOwnerWallet

func (s *Service) RegisterOwnerWallet(id string, path string) error

func (*Service) RegisterRecipientIdentity

func (s *Service) RegisterRecipientIdentity(id view.Identity, auditInfo []byte, metadata []byte) error

func (*Service) SpentIDs

func (s *Service) SpentIDs(ids ...*token.ID) ([]string, error)

SpentIDs returns the spend ids for the passed token ids

func (*Service) Transfer

func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token2.ID, Outputs []*token2.Token, opts *driver.TransferOptions) (driver.TransferAction, *driver.TransferMetadata, error)

Transfer returns a TransferAction as a function of the passed arguments It also returns the corresponding TransferMetadata

func (*Service) Validator

func (s *Service) Validator() driver.Validator

func (*Service) VerifyCertifications

func (s *Service) VerifyCertifications(ids []*token2.ID, certifications [][]byte) error

VerifyCertifications checks if the passed certifications are valid with respect to the tokens associated with the passed identifiers If not, VerifyCertifications returns an error fabtoken does not make use of the certification service

func (*Service) VerifyIssue

func (s *Service) VerifyIssue(tr driver.IssueAction, tokenInfos [][]byte) error

VerifyIssue checks if the outputs of an IssueAction match the passed tokenInfos

func (*Service) VerifyTransfer

func (s *Service) VerifyTransfer(tr driver.TransferAction, outputsMetadata [][]byte) error

VerifyTransfer checks the outputs in the TransferAction against the passed tokenInfos

func (*Service) Wallet

func (s *Service) Wallet(identity view.Identity) driver.Wallet

type TokenLoader

type TokenLoader interface {
	GetTokens(ids []*token2.ID) ([]string, []*token2.Token, error)
}

type TokenVault

type TokenVault interface {
	PublicParams() ([]byte, error)
}

type TransferAction

type TransferAction struct {
	// identifier of token to be transferred
	Inputs []string
	// outputs to be created as a result of the transfer
	Outputs []*Output
	// Metadata contains the transfer action's metadata
	Metadata map[string][]byte
}

TransferAction encodes a fabtoken transfer

func UnmarshalTransferActions

func UnmarshalTransferActions(raw [][]byte) ([]*TransferAction, error)

UnmarshalTransferActions returns an array of deserialized TransferAction from raw bytes

func (*TransferAction) Deserialize

func (t *TransferAction) Deserialize(raw []byte) error

Deserialize un-marshals TransferAction

func (*TransferAction) GetInputs

func (t *TransferAction) GetInputs() ([]string, error)

GetInputs returns inputs of the TransferAction

func (*TransferAction) GetMetadata

func (t *TransferAction) GetMetadata() map[string][]byte

GetMetadata returns the transfer action's metadata

func (*TransferAction) GetOutputs

func (t *TransferAction) GetOutputs() []driver.Output

GetOutputs returns the outputs in a TransferAction

func (*TransferAction) GetSerializedOutputs

func (t *TransferAction) GetSerializedOutputs() ([][]byte, error)

GetSerializedOutputs returns the serialization of the outputs in a TransferAction

func (*TransferAction) IsGraphHiding

func (t *TransferAction) IsGraphHiding() bool

IsGraphHiding returns false, indicating that fabtoken does not hide the transaction graph

func (*TransferAction) IsRedeemAt

func (t *TransferAction) IsRedeemAt(index int) bool

IsRedeemAt returns true if the output at the specified index is a redeemed output todo update interface to account for nil t.outputs[index]

func (*TransferAction) NumOutputs

func (t *TransferAction) NumOutputs() int

NumOutputs returns the number of outputs in an TransferAction

func (*TransferAction) Serialize

func (t *TransferAction) Serialize() ([]byte, error)

Serialize marshals TransferAction

func (*TransferAction) SerializeOutputAt

func (t *TransferAction) SerializeOutputAt(index int) ([]byte, error)

SerializeOutputAt marshals the output at the specified index in TransferAction

type ValidateTransferFunc

type ValidateTransferFunc func(ctx *Context) error

ValidateTransferFunc is the prototype of a validation function for a transfer action

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator checks the validity of fabtoken TokenRequest

func NewValidator

func NewValidator(pp *PublicParams, deserializer driver.Deserializer, extraValidators ...ValidateTransferFunc) (*Validator, error)

NewValidator initializes a Validator with the passed parameters

func (*Validator) UnmarshalActions

func (v *Validator) UnmarshalActions(raw []byte) ([]interface{}, error)

UnmarshalActions returns the actions contained in the serialized token request

func (*Validator) VerifyAuditorSignature

func (v *Validator) VerifyAuditorSignature(signatureProvider driver.SignatureProvider) error

VerifyAuditorSignature checks if the content of the token request concatenated with the binding was signed by the authorized auditor

func (*Validator) VerifyIssue

func (v *Validator) VerifyIssue(issue driver.IssueAction) error

VerifyIssue checks if all outputs in IssueAction are valid (no zero-value outputs)

func (*Validator) VerifyIssues

func (v *Validator) VerifyIssues(issues []*IssueAction, signatureProvider driver.SignatureProvider) error

VerifyIssues checks if the issued tokens are valid and if the content of the token request concatenated with the binding was signed by one of the authorized issuers

func (*Validator) VerifyTokenRequest

func (v *Validator) VerifyTokenRequest(ledger driver.Ledger, signatureProvider driver.SignatureProvider, binding string, tr *driver.TokenRequest) ([]interface{}, error)

VerifyTokenRequest validates the passed token request against data in the ledger, the signature provided and the binding

func (*Validator) VerifyTokenRequestFromRaw

func (v *Validator) VerifyTokenRequestFromRaw(getState driver.GetStateFnc, binding string, raw []byte) ([]interface{}, error)

VerifyTokenRequestFromRaw validates the raw token request

func (*Validator) VerifyTransfer

func (v *Validator) VerifyTransfer(ledger driver.Ledger, inputTokens []*token2.Token, tr driver.TransferAction, signatureProvider driver.SignatureProvider) error

VerifyTransfer checks that sum of inputTokens in TransferAction equals sum of outputs in TransferAction It also checks that all outputs and inputs have the same type

func (*Validator) VerifyTransfers

func (v *Validator) VerifyTransfers(ledger driver.Ledger, transferActions []*TransferAction, signatureProvider driver.SignatureProvider) error

VerifyTransfers checks if the created output tokens are valid and if the content of the token request concatenated with the binding was signed by the owners of the input tokens

type VaultPublicParamsLoader

type VaultPublicParamsLoader struct {
	PublicParamsFetcher driver.PublicParamsFetcher
	PPLabel             string
}

VaultPublicParamsLoader allows one to fetch the public parameters for fabtoken

func (*VaultPublicParamsLoader) Fetch

func (s *VaultPublicParamsLoader) Fetch() ([]byte, error)

Fetch fetches the public parameters from the backend

func (*VaultPublicParamsLoader) FetchParams

func (s *VaultPublicParamsLoader) FetchParams() (*PublicParams, error)

FetchParams fetches the public parameters from the backend and unmarshal them

type VaultTokenLoader

type VaultTokenLoader struct {
	TokenVault driver.QueryEngine
}

func (*VaultTokenLoader) GetTokens

func (s *VaultTokenLoader) GetTokens(ids []*token.ID) ([]string, []*token.Token, error)

GetTokens takes an array of token identifiers (txID, index) and returns the keys of the identified tokens in the vault and the content of the tokens

type VerifierDES

type VerifierDES interface {
	DeserializeVerifier(id view.Identity) (driver.Verifier, error)
}

VerifierDES is the interface for verifiers' deserializer A verifier checks the validity of a signature against the identity associated with the verifier

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL