diff --git a/lnpbp-0021.md b/lnpbp-0021.md index 849a191..7c5932a 100644 --- a/lnpbp-0021.md +++ b/lnpbp-0021.md @@ -1,11 +1,14 @@ ``` LNPBP: 0021 Vertical: Smart contracts -Title: RGB non-fungible assets schema for collectibles (RGB-21) -Authors: Dr Maxim Orlovsky , +Title: RGB non-fungible assets interface for collectibles (RGB-21) +Authors: Dr Maxim Orlovsky , + Hunter Trujillo, + Federico Tenga, + Zoe FaltibĂ , Carlos Roldan, + Olga Ukolova, Giacomo Zucco, - Olgsa Ukolova Comments-URI: Status: Proposal Type: Standards Track @@ -13,201 +16,159 @@ Created: 2020-09-10 License: CC0-1.0 ``` -- [x] Unique tokens & token-specfic data -- [x] Issue control -- [x] Generic token data, internal or external, in different formats -- [x] Engravings (any why Schema subclassing is required) -- [x] LockUTXOs and descriptors for proof of reserves -- [x] Renominations -- [x] Rights splits - -```rust -Schema { - rgb_features: none!(), - root_id: none!(), - genesis: GenesisSchema { - metadata: type_map! { - FieldType::Name => Once, - FieldType::Description => NoneOrOnce, - FieldType::Data => NoneOrMore, - FieldType::DataFormat => NoneOrOnce, - // Proof of reserves UTXO - FieldType::LockUtxo => NoneOrMore, - // Proof of reserves scriptPubkey descriptor used for - // verification - FieldType::LockDescriptor => NoneOrUpTo(32), - FieldType::Timestamp => Once, - FieldType::NftSource => NoneOrMore, - FieldType::Salt => Once - }, - owned_rights: type_map! { - OwnedRightsType::Inflation => NoneOrOnce, - OwnedRightsType::Renomination => NoneOrOnce, - OwnedRightsType::Ownership => OnceOrMore - }, - public_rights: none!(), - abi: bmap! { - // Here we validate hash uniqueness of NftSource values and that - // there is always one ownership state per NftSource matching - // its hash - // and verification of proof of reserves - GenesisAction::Validate => Procedure::Embedded(StandardProcedure::NonfungibleInflation) - }, - }, - extensions: bmap! {}, - transitions: type_map! { - TransitionType::Issue => TransitionSchema { - metadata: type_map! { - // Proof of reserves UTXO - FieldType::LockUtxo => NoneOrMore, - // Proof of reserves scriptPubkey descriptor used for - // verification - FieldType::LockDescriptor => NoneOrUpTo(32), - FieldType::NftSource => NoneOrMore, - FieldType::Salt => Once - }, - closes: type_map! { - OwnedRightsType::Inflation => Once - }, - owned_rights: type_map! { - OwnedRightsType::Inflation => NoneOrOnce, - OwnedRightsType::Ownership => OnceOrMore - }, - public_rights: none!(), - abi: bmap! { - // Here we validate hash uniqueness of NftSource values and that - // there is always one ownership state per NftSource matching - // its hash, plus the fact that - // count(in(inflation)) >= count(out(inflation), out(nft_source)) - // and verification of proof of reserves - TransitionAction::Validate => Procedure::Embedded(StandardProcedure::NonfungibleInflation) - } - }, - TransitionType::Transfer => TransitionSchema { - metadata: type_map! { - // By default, use 0 - FieldType::Salt => Once - }, - closes: type_map! { - OwnedRightsType::Ownership => OnceOrMore - }, - owned_rights: type_map! { - OwnedRightsType::Ownership => OnceOrMore - }, - public_rights: none!(), - abi: none!() - }, - // One engraving per set of tokens - TransitionType::Engraving => TransitionSchema { - metadata: type_map! { - FieldType::Data => NoneOrMore, - FieldType::DataFormat => NoneOrOnce, - // By default, use 0 - FieldType::Salt => Once - }, - closes: type_map! { - OwnedRightsType::Ownership => OnceOrMore - }, - owned_rights: type_map! { - OwnedRightsType::Ownership => OnceOrMore - }, - public_rights: none!(), - abi: none!() - }, - TransitionType::Renomination => TransitionSchema { - metadata: type_map! { - FieldType::Name => NoneOrOnce, - FieldType::Description => NoneOrOnce, - FieldType::Data => NoneOrMore, - FieldType::DataFormat => NoneOrOnce - }, - closes: type_map! { - OwnedRightsType::Renomination => Once - }, - owned_rights: type_map! { - OwnedRightsType::Renomination => NoneOrOnce - }, - public_rights: none!(), - abi: none!() - }, - // Allows split of rights if they were occasionally allocated to the - // same UTXO, for instance both assets and issuance right. Without - // this type of transition either assets or inflation rights will be - // lost. - TransitionType::RightsSplit => TransitionSchema { - metadata: type_map! { - FieldType::Salt => Once - }, - closes: type_map! { - OwnedRightsType::Inflation => NoneOrMore, - OwnedRightsType::Ownership => NoneOrMore, - OwnedRightsType::Renomination => NoneOrOnce - }, - owned_rights: type_map! { - OwnedRightsType::Inflation => NoneOrMore, - OwnedRightsType::Ownership => NoneOrMore, - OwnedRightsType::Renomination => NoneOrOnce - }, - public_rights: none!(), - abi: bmap! { - // We must allocate exactly one or none rights per each - // right used as input (i.e. closed seal); plus we need to - // control that sum of inputs is equal to the sum of outputs - // for each of state types having assigned confidential - // amounts - TransitionAction::Validate => Procedure::Embedded(StandardProcedure::RightsSplit) - } - } - }, - field_types: type_map! { - FieldType::Name => DataFormat::String(256), - FieldType::Description => DataFormat::String(core::u16::MAX), - FieldType::Data => DataFormat::Bytes(core::u16::MAX), - FieldType::DataFormat => DataFormat::Unsigned(Bits::Bit16, 0, core::u16::MAX as u128), - // While UNIX timestamps allow negative numbers; in context of RGB - // Schema, assets can't be issued in the past before RGB or Bitcoin - // even existed; so we prohibit all the dates before RGB release - // This timestamp is equal to 10/10/2020 @ 2:37pm (UTC) - the same - // as for RGB-20 standard. - FieldType::Timestamp => DataFormat::Integer(Bits::Bit64, 1602340666, core::i64::MAX as i128), - FieldType::LockUtxo => DataFormat::TxOutPoint, - FieldType::LockDescriptor => DataFormat::String(core::u16::MAX), - FieldType::BurnUtxo => DataFormat::TxOutPoint, - // This type is used to "shift" unique tokens ids if there was a - // collision between them - FieldType::Salt => DataFormat::Unsigned(Bits::Bit32, 0, core::u32::MAX as u128), - // Hash of these data serves as a unique NFT identifier; - // if NFT contains no intrinsic data than simply put any unique - // value here (like counter value, increased with each token); - // it must be unique only within single issuance transition - FieldType::NftSource => DataFormat::Bytes(core::u16::MAX) - }, - owned_right_types: type_map! { - OwnedRightsType::Inflation => StateSchema { - // How much issuer can issue tokens on this path - format: StateFormat::CustomData(DataFormat::Unsigned(Bits::Bit64, 0, core::u64::MAX as u128)), - abi: none!() - }, - OwnedRightsType::Ownership => StateSchema { - // This is unique token identifier, which is - // SHA256(SHA256(nft_source_state), issue_transition_id) - // convoluted to 32-bits with XOR operation and then XORed with - // salt value from state transition metadata. - // NB: It is unique inside single state transition only, not - // globally. For global unique id use non-convoluted hash value. - format: StateFormat::CustomData(DataFormat::Unsigned(Bits::Bit32, 0, core::u32::MAX as u128)), - abi: bmap! { - // Here we ensure that each unique state value is - // transferred once and only once (using "salt" value for - // collision resoultion) - AssignmentAction::Validate => Procedure::Embedded(StandardProcedure::IdentityTransfer) - } - }, - OwnedRightsType::Renomination => StateSchema { - format: StateFormat::Declarative, - abi: none!() - } - }, - public_right_types: none!(), -} +- [Abstract](#abstract) +- [Background](#background) +- [Motivation](#motivation) +- [Design](#design) +- [Specification](#specification) +- [Compatibility](#compatibility) +- [Rationale](#rationale) +- [Reference implementation](#reference-implementation) +- [Acknowledgements](#acknowledgements) +- [References](#references) +- [Copyright](#copyright) +- [Test vectors](#test-vectors) + + +## Abstract + + +## Background + + +## Motivation + + +## Design + +- [x] Media as URI (which can be attachments distributed with Storm or usual URLs) +- [x] Small media ("previews" if given together with large media) +- [x] Fraction of assets +- [x] Engravings +- [x] Reserves +- [x] Possible decentralized issue + + +## Specification + +Interface specification is the following Contractum code: + +```haskell +-- # Defining main data structures + +-- collectibles are usually scarse, so we limit their max number to 64k +data ItemsCount :: U32 + +-- each collectible item is unique and must have an id +data TokenId :: U16 + +-- Collectibles are owned in fractions of a single item; here the owned +-- fraction means `1/F`. Ownership of the full item is specified `F=1`; +-- half of an item as `F=2` etc. +data OwnedFraction :: U64 + +data TxOut :: txid [Byte ^ 32], vout U16 + +-- allocation of a single token or its fraction to some transaction output +data Allocation :: TokenId, OwnedFraction + +data Engraving :: appliedTo TokenId, content Media + +data Media :: + type MimeType, + data [Byte] + +data Attachment :: + type MimeType, + digest: [U8 ^ 32] -- this can be any type of 32-byte hash, like SHA256(d), BLACKE3 etc + +data POR :: -- proof of reserves + reserves TxOut, + proof [Byte] -- schema-specific proof + +data Token :: + id TokenId, + name [Ascii ^ 1..40], + details [Unicode ^ 40..256]?, + preview Media?, -- always embedded preview media < 64kb + media Attachment?, -- external media which is the main media for the token + attachments { U8 -> ^ ..20 Attachment } -- auxiliary attachments by type (up to 20 attachments) + reserves POR? -- output containing locked bitcoins; how reserves are + -- proved is a matter of a specific schema implementation + +data Denomination :: + ticker [Ascii ^ 1..8], + name [Ascii ^ 1..40], + details [Unicode ^ 40..256]?, + contract [Unicode]??, + + -- each attachment type is a mapping from attachment id + -- (used as `Token.attachments` keys) to a short Ascii string + -- verbally explaining the type of the attachment for the UI + -- (like "sample" etc). +data AttachmentType :: id U8, description [Ascii ^ 1..20] + + +interface RGB21 + global Name :: Denomination + global Tokens :: Token+ + global Engravings :: Engraving+ + global isFractional :: Bool + global AttachmentTypes :: AttachmentType+ + + owned Allocations+ :: Allocation + owned IssueRight* :: Amount + owned DenominationRight? + -- returns information about known circulating supply + read supplyKnown :: Amount + count Self.state["Tokens"] + -- sum Self.ops["issue"]..closed["usingRight"].state ?? 0 + + -- maximum possible asset inflation + read supplyLimit :: Amount + sum Self.IssueRight..state ?? 0 + + read isCirculationKnown :: Bool + all Self.ops["issue"]..stateKnown + + op transfer :: inputs [Allocation+] -> beneficiaries [Allocation] + !! -- options for operation failure: + inequalFractions(TokenId) + | nonFractionalToken + + -- question mark denotes optional operation, which may not be supported by + -- some of schemata implementing the interface + + op? engrave :: Allocation -> Allocation + <- Engraving + + op? issue :: IssueRight -> IssueRight, beneficiaries {Allocation} + <- tokens {Token}, newAttachmentTypes {attachmentType}* + !! invalidReserves(POR) + + -- decentralized issue + op? decentralizedIssue -> tokens {Token}, beneficiaries {Allocation} + !! invalidReserves(POR) + + op? rename :: DenominationRight -> DenominationRight? <- Denomination + <- Nomination ``` + +## Compatibility + + +## Rationale + + +## Reference implementation + + +## Acknowledgements + + +## References + + +## Copyright + +This document is licensed under the Creative Commons CC0 1.0 Universal license.