Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite RGB21 spec using Contractum & concept of interfaces #137

Merged
merged 10 commits into from
Apr 20, 2023
370 changes: 170 additions & 200 deletions lnpbp-0021.md
Original file line number Diff line number Diff line change
@@ -1,213 +1,183 @@
```
LNPBP: 0021
Vertical: Smart contracts
Title: RGB non-fungible assets schema for collectibles (RGB-21)
Authors: Dr Maxim Orlovsky <[email protected]>,
Title: RGB non-fungible assets interface for collectibles (RGB-21)
Authors: Dr Maxim Orlovsky <[email protected]>,
Hunter Trujillo,
Federico Tenga,
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
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

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
data Media ::
type MimeType,
data [Byte]

data Attachment ::
type MimeType,
uri: Uri
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved

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 } -- auxillary attachments by type (up to 20 attachments)
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
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]??,

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
data Auxiliary ::
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
-- list of allowed sources
sources [Source]

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
-- 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).
attachmentTypes { U8 ^ ..32 -> [Ascii ^ 1..20] }
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved

data Source :: url(Proto, Dns) | urn(UrnPrefix) | storm(NodeAddr)
data Proto :: http | https | httpxk | ws | wss | wssxk
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved

interface RGB21
global Name :: Denomination
global Tokens :: [(Token, [Engraving])]
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
global isFractional :: Bool
global Registry :: Auxiliary
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
owned Allocations+ :: Allocation
owned IssueRight* :: Amount
owned DenominationRight?

owned ControlRight

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
-- 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}
!! invalidReserves(POR)
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved

-- decentralized issue
op? decentralizedIssue -> tokens {Token}, beneficiaries {Allocation}
!! invalidReserves(POR)

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
-- updates registry of auxiliary data to a new version
op? update :: controlRight -> controlRight
<- Registry

dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
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.