-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #137 from LNP-BP/rgb21
Rewrite RGB21 spec using Contractum & concept of interfaces
- Loading branch information
Showing
1 changed file
with
161 additions
and
200 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,213 +1,174 @@ | ||
``` | ||
LNPBP: 0021 | ||
Vertical: Smart contracts | ||
Title: RGB non-fungible assets schema for collectibles (RGB-21) | ||
Authors: Dr Maxim Orlovsky <orlovsky@protonmail.ch>, | ||
Title: RGB non-fungible assets interface for collectibles (RGB-21) | ||
Authors: Dr Maxim Orlovsky <orlovsky@lnp-bp.org>, | ||
Hunter Trujillo, | ||
Federico Tenga, | ||
Zoe Faltibà, | ||
Carlos Roldan, | ||
Olga Ukolova, | ||
Giacomo Zucco, | ||
Olgsa Ukolova | ||
Comments-URI: <https://github.com/LNP-BP/LNPBPs/issues/70> | ||
Status: Proposal | ||
Type: Standards Track | ||
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. |