ARC-19: Templating of NFT ASA URLs for mutability Source

Templating mechanism of the URL so that changeable data in an asset can be substituted by a client, providing a mutable URL.

AuthorPatrick Bennett / TxnLab Inc.
Discussions-Tohttps://github.com/algorandfoundation/ARCs/issues/73
StatusFinal
TypeStandards Track
CategoryARC
Created2021-01-23

Abstract

This ARC describes a template substitution for URLs in ASAs, initially for ipfs:// scheme URLs allowing mutable CID replacement in rendered URLs.

The proposed template-XXX scheme has substitions like:

template-ipfs://{ipfscid:<version>:<multicodec>:<field name containing 32-byte digest, ie reserve>:<hash type>}[/...]

This will allow modifying the 32-byte ‘Reserve address’ in an ASA to represent a new IPFS content-id hash. Changing of the reserve address via an asset-config transaction will be all that is needed to point an ASA URL to new IPFS content. The client reading this URL, will compose a fully formed IPFS Content-ID based on the version, multicodec, and hash arguments provided in the ipfscid substitition.

Motivation

While immutability for many NFTs is appropriate (see ARC-3 link), there are cases where some type of mutability is desired for NFT metadata and/or digital media. The data being referenced by the pointer should be immutable but the pointer may be updated to provide a kind of mutability. The data being referenced may be of any size.

Algorand ASAs support mutation of several parameters, namely the role address fields (Manager, Clawback, Freeze, and Reserve addresses), unless previously cleared. These are changed via an asset-config transaction from the Manager account. An asset-config transaction may include a note, but it is limited to 1KB and accessing this value requires clients to use an indexer to iterate/retrieve the values.

Of the parameters that are mutable, the Reserve address is somewhat distinct in that it is not used for anything directly as part of the protocol. It is used solely for determining what is in/out of circulation (by subtracting supply from that held by the reserve address). With a (pure) NFT, the Reserve address is irrelevant as it is a 1 of 1 unit. Thus, the Reserve address may be repurposed as a 32-byte ‘bitbucket’.

These 32-bytes can, for example, hold a SHA2-256 hash uniquely referencing the desired content for the ASA (ARC-3-like metadata for example)

Using the reserve address in this way means that what an ASA ‘points to’ for metadata can be changed with a single asset config transaction, changing only the 32-bytes of the reserve address. The new value is accessible via even non-archival nodes with a single call to the /v2/assets/xxx REST endpoint.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC-2119.

This proposal specifies a method to provide mutability for IPFS hosted content-ids. The intention is that FUTURE ARCs could define additional template substitutions, but this is not meant to be a kitchen sink of templates, only to establish a possible baseline of syntax.

An indication that this ARC is in use is defined by an ASA URL’s “scheme” having the prefix “template-”.

An Asset conforming this specification MUST have:

  1. URL Scheme of “template-ipfs”

The URL of the asset must be of the form:

template-ipfs://(...)

The ipfs:// scheme is already somewhat of a meta scheme in that clients interpret the ipfs scheme as referencing an IPFS CID (version 0/base58 or 1/base32 currently) followed by optional path within certain types of IPFS DAG content (IPLD CAR content for example). The clients take the CID and use to fetch directly from the IPFS network directly via IPFS nodes, or via various IPFS gateways (https://ipfs.io/ipfs/CID[/…], pinata, etc.)).

  1. An “ipfscid” template argument in place of the normal CID.

Where the format of templates are {<template type>:<parameters...>])

The ipfscid template definitions is based on properties within the IPFS CID spec: https://github.com/multiformats/cid

ipfscid:<version>:<multicodec content-type name>:<field name (containing 32-byte digest, i.e., "reserve")>:<hash type>

The intent is to recompose a complete CID based on the content-hash contained within the 32-byte reserve address, but using the correct multicodec content type, ipfs content-id version, and hash type to match how the asset creator will seed the IPFS content. If a single file is added using the ‘ipfs’ CLI via ipfs add --cid-version=1 metadata.json then the resulting content will be encoded using the ‘raw’ multicodec type. If a directory is added containing one or more files, then it will be encoded using the dag-pb multicodec. CAR content will also be dag-pb. Thus based on the method used to post content to IPFS, the ipfscid template should match.

The parameters to the template ipfscid are:

  1. IPFS <version>, MUST a valid IPFS CID version. Client implementation MUST support ‘0’ or ‘1’ and SHOULD support future version.
  2. <multicodec content-type name> MUST be an IPFS multicodec name. Client implementations MUST support ‘raw’ or ‘dag-pb’. Other codecs SHOULD be supported but are beyond the scope of this proposal.
  3. <field name> MUST be ‘reserve’.

    This is to represent the reserve address is used for the 32-byte hash. It is specified here so future iterations of the specification may allow other fields or syntaxes to reference other mutable field types.

  4. <hash type> MUST be the multihash hash function type (as defined in https://multiformats.io/multihash/). Client implementations MUST support ‘sha2-256’ and SHOULD support future hash types when introduced by IPFS.

IPFS may add future versions of the cid spec, and add additional multicodec types or hash types.

Implementations SHOULD use IPFS libraries where possible that accept multicodec and hash types as named values and allow a CID to be composed generically.

Examples

This whole section is non-normative.

  • ASA URL: template-ipfs://{ipfscid:0:dag-pb:reserve:sha2-256}/arc3.json
  • ASA URL: template-ipfs://{ipfscid:1:raw:reserve:sha2-256}
  • ASA URL: template-ipfs://{ipfscid:1:dag-pb:reserve:sha2-256}/metadata.json

Deployed Testnet Example

An example was pushed to TestNet, converting from an existing ARC-3 MainNet ASA (asset ID 560421434, https://algoexplorer.io/asset/560421434)

With IPFS URL:

ipfs://QmQZyq4b89RfaUw8GESPd2re4hJqB8bnm4kVHNtyQrHnnK

The TestNet ASA was minted with the URL:

template-ipfs://{ipfscid:0:dag-pb:reserve:sha2-256}

as the original CID is a V0 / dag-pb CID.

A helpful link to ‘visualize’ CIDs and for this specific id, is https://cid.ipfs.io/#QmQZyq4b89RfaUw8GESPd2re4hJqB8bnm4kVHNtyQrHnnK

Using the example encoding implementation, results in virtual ‘reserve address’ of

EEQYWGGBHRDAMTEVDPVOSDVX3HJQIG6K6IVNR3RXHYOHV64ZWAEISS4CTI

which is the address (with checksum) corresponding to the 32-byte with hexadecimal value:

21218B18C13C46064C951BEAE90EB7D9D3041BCAF22AD8EE373E1C7AFB99B008

(Transformation from a 32-byte public key to an address can be found there on the developer website https://developer.algorand.org/docs/get-details/accounts/#transformation-public-key-to-algorand-address.)

The resulting ASA can be seen on https://testnet.algoexplorer.io/asset/66753108

Using the forked repo, with testnet selected, and the /nft/66753108 url - the browser will display the original content as-is, using only the Reserve address as the source of the content hash.

Interactions with ARC-3

This ARC is compatible with ARC-3 with the following notable exception: the ASA Metadata Hash (am) is no more necessarily a valid hash of the JSON Metadata File pointed by the URL.

As such, clients cannot be strictly compatible to both ARC-3 and ARC-19. An ARC-3 and ARC-19 client SHOULD ignore validation of the ASA Metadata Hash when the Asset URL is following ARC-19.

ARC-3 clients SHOULD clearly indicate to the user when displaying an ARC-19 ASA, as contrary to a strict ARC-3 ASA, the asset may arbitrarily change over time (even after being bought).

ASA that follow both ARC-3 and ARC-19 MUST NOT use extra metadata hash (from ARC-3).

Rationale

See the motivation section above for the general rationale.

Backwards Compatibility

The ‘template-‘ prefix of the scheme is intended to break clients reading these ASA URLs outright. Clients interpreting these URLs as-is would likely yield unusual errors. Code checking for an explicit ‘ipfs’ scheme for example will not see this as compatible with any of the default processing and should treat the URL as if it were simply unknown/empty.

Reference Implementation

Encoding

Go implementation

import (
    "github.com/algorand/go-algorand-sdk/types"
    "github.com/ipfs/go-cid"
    "github.com/multiformats/go-multihash"
)
// ...
func ReserveAddressFromCID(cidToEncode cid.Cid) (string, error) {
    decodedMultiHash, err := multihash.Decode(cidToEncode.Hash())
    if err != nil {
        return "", fmt.Errorf("failed to decode ipfs cid: %w", err))
    }
    return types.EncodeAddress(decodedMultiHash.Digest)
}
// ....

Decoding

Go implementation

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/algorand/go-algorand-sdk/types"
	"github.com/ipfs/go-cid"
	"github.com/multiformats/go-multicodec"
	"github.com/multiformats/go-multihash"
)

var (
	ErrUnknownSpec      = errors.New("unsupported template-ipfs spec")
	ErrUnsupportedField = errors.New("unsupported ipfscid field, only reserve is currently supported")
	ErrUnsupportedCodec = errors.New("unknown multicodec type in ipfscid spec")
	ErrUnsupportedHash  = errors.New("unknown hash type in ipfscid spec")
	ErrInvalidV0        = errors.New("cid v0 must always be dag-pb and sha2-256 codec/hash type")
	ErrHashEncoding     = errors.New("error encoding new hash")
	templateIPFSRegexp  = regexp.MustCompile(`template-ipfs://{ipfscid:(?P<version>[01]):(?P<codec>[a-z0-9\-]+):(?P<field>[a-z0-9\-]+):(?P<hash>[a-z0-9\-]+)}`)
)

func ParseASAUrl(asaUrl string, reserveAddress types.Address) (string, error) {
	matches := templateIPFSRegexp.FindStringSubmatch(asaUrl)
	if matches == nil {
		if strings.HasPrefix(asaUrl, "template-ipfs://") {
			return "", ErrUnknownSpec
		}
		return asaUrl, nil
	}
	if matches[templateIPFSRegexp.SubexpIndex("field")] != "reserve" {
		return "", ErrUnsupportedField
	}
	var (
		codec         multicodec.Code
		multihashType uint64
		hash          []byte
		err           error
		cidResult     cid.Cid
	)
	if err = codec.Set(matches[templateIPFSRegexp.SubexpIndex("codec")]); err != nil {
		return "", ErrUnsupportedCodec
	}
	multihashType = multihash.Names[matches[templateIPFSRegexp.SubexpIndex("hash")]]
	if multihashType == 0 {
		return "", ErrUnsupportedHash
	}

	hash, err = multihash.Encode(reserveAddress[:], multihashType)
	if err != nil {
		return "", ErrHashEncoding
	}
	if matches[templateIPFSRegexp.SubexpIndex("version")] == "0" {
		if codec != multicodec.DagPb {
			return "", ErrInvalidV0
		}
		if multihashType != multihash.SHA2_256 {
			return "", ErrInvalidV0
		}
		cidResult = cid.NewCidV0(hash)
	} else {
		cidResult = cid.NewCidV1(uint64(codec), hash)
	}
	return fmt.Sprintf("ipfs://%s", strings.ReplaceAll(asaUrl, matches[0], cidResult.String())), nil
}

Typescript Implementation

A modified version of a simple ARC-3 viewer can be found here specifically the code segment at nft.ts#L41

This is a fork of ar3.xyz

Security Considerations

There should be no specific security issues beyond those of any client accessing any remote content and the risks linked to assets changing (even after the ASA is bought).

The later is handled in the section “Interactions with ARC-3” above.

Regarding the former, URLs within ASAs could point to malicious content, whether that is an http/https link or whether fetched through ipfs protocols or ipfs gateways. As the template changes nothing other than the resulting URL and also defines nothing more than the generation of an IPFS CID hash value, no security concerns derived from this specific proposal are known.

Copyright and related rights waived via CCO.

Citation

Please cite this document as:

Patrick Bennett / TxnLab Inc., "ARC-19: Templating of NFT ASA URLs for mutability," Algorand Requests for Comments, no. 19, January 2021. [Online serial]. Available: https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0019.md.