Skip to content
Snippets Groups Projects

[doc] externalize protocols documentation

Merged Éloïs requested to merge externalize_protocol_doc into dev
All threads resolved!
1 file
+ 5
2874
Compare changes
  • Side-by-side
  • Inline
+ 5
2874
# DUP - Duniter Protocol
# DUniter Protocols
> This document reflects Duniter in-production protocol. It is updated only for clarifications (2017).
## DUniter Blockchain Protocol (DUBP)
## Contents
See [RFC 0009: Duniter Blockchain Protocol V11](https://git.duniter.org/nodes/common/doc/blob/master/rfc/0009_Duniter_Blockchain_Protocol_V11.md).
* [Contents](#contents)
* [Introduction](#introduction)
* [Conventions](#conventions)
* [Documents](#documents)
* [Signatures](#signatures)
* [Formats](#formats)
* [Public key](#public-key)
* [Identity](#identity)
* [Revocation](#revocation)
* [Certification](#certification)
* [Membership](#membership)
* [Block](#block)
* [Transaction](#transaction)
* [Peer](#peer)
* [Variables](#variables)
* [Protocol parameters](#protocol-parameters)
* [Computed variables](#computed-variables)
* [Processing](#processing)
* [Block](#block-1)
* [Peer](#peer-1)
* [Transaction](#transaction-1)
* [Implementations](#implementations)
* [References](#references)
## DUniter Network Protocol (DUNP)
## Vocabulary
Word | Description
--------------------- | -------------
UCP | Acronym for *UCoin Protocol*. A set of rules to create Duniter based currencies.
Signature | The cryptographical act of certifying a document using a private key.
WoT | Acronym for *Web of Trust*. A groupment of individuals recognizing each other's identity through public keys and certification mechanisms
UD | Acronym for *Universal Dividend*. Means money issuance **directly** and **exclusively** by and to WoT members
realtime | The time of real life (the habitual time).
blocktime | The realtime recorded by a block.
## Introduction
UCP aims at defining a data format, an interpretation of it and processing rules in order to build coherent free currency systems in a P2P environment. UCP is to be understood as an *abstract* protocol since it defines currency parameters and rules about them, but not their value which is implementation specific.
This document describes UCP in a bottom-up logic, so you will find first the details of the protocol (data format) to end with general protocol requirements.
## Conventions
### Documents
#### Line endings
Please note **very carefully** that every document's line **ENDS with a newline character**, *Unix-style*, that is to say `<LF>`.
This is a *very important information* as every document is subject to hashes, and Windows-style endings won't produce the expected hashes.
#### Numbering
[Block](#block) numbering starts from `0`. That is, first block is `BLOCK#0`.
#### Block identifiers
There are 2 kinds of [Block](#block) identifiers:
##### `BLOCK_ID`
It is the number of the block. Its format is `INTEGER`, so it is a positive or zero integer.
##### `BLOCK_UID`
It is the concatenation of the `BLOCK_ID` of a block and hash. Its format is `BLOCK_ID-HASH`.
##### Examples
The block ID of the root block is `0`. Its UID *might be* `0-883228A23F8A75B342E912DF439738450AE94F5D`.
The block ID of the block#433 is `433`. Its UID *might be* `433-FB11681FC1B3E36C9B7A74F4DDE2D07EC2637957`.
> Note that it says "might be" because the hash is not known in advance.
#### Currency name
A valid currency name is composed of alphanumeric characters, spaces, `-` or `_` and has a length of 2 to 50 characters.
#### Datesœ
For any document using a date field, targeted date is to be understood as **UTC+0** reference.
#### Integer
Any integer field has a maximum length of 19 digits.
### Signatures
#### Format
Signatures follow the [Ed25519 pattern](http://en.wikipedia.org/wiki/EdDSA), and are written under [Base64](http://en.wikipedia.org/wiki/Base64) encoding.
Here is an example of an expected signature:
H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==
#### Line endings
No new line character exists in a signature. However, a signature may be followed by a new line character, hence denoting the end of the signature.
## Formats
This section deals with the various data formats used by UCP.
### Public key
#### Definition
A public key is to be understood as an [Ed25519](http://en.wikipedia.org/wiki/EdDSA) public key.
Its format is a [Base58](http://en.wikipedia.org/wiki/Base58) string of 43 or 44 characters, such as the following:
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
A public key is always paired with a private key, which UCP will never deal with. UCP only deals with public keys and signatures.
### Identity
#### Definition
Issuing an identity is the act of creating a link between a *public key* and *an arbitrary identity*. In UCP, this link is done through the signature of an identity string by the private key corresponding to the public key. It is exactly like saying:
> « This identity refers to me ! »
#### Identity unique ID
UCP does not rely on any particular identity format, which remains implementation free. Identity simply has to be a string of length between 2 and 100 characters avoiding usage of line ending characters.
In this document *identifier*, `UserID`, `USER_ID` and `uid` will be indifferently used to refer to this identity string.
#### Format
An identity is a *signed* document containing the identifier:
```yml
Version: 10
Type: Identity
Currency: CURRENCY_NAME
Issuer: PUBLIC_KEY
UniqueID: USER_ID
Timestamp: BLOCK_UID
```
Here, `USER_ID` has to be replaced with a valid identifier, `PUBLIC_KEY` with a valid public key and `BLOCK_UID` with a valid block unique ID. This document **is what signature is based upon**.
The whole identity document is then:
```yml
Version: 10
Type: Identity
Currency: CURRENCY_NAME
Issuer: PUBLIC_KEY
UniqueID: USER_ID
Timestamp: BLOCK_UID
SIGNATURE
```
Where:
* `CURRENCY_NAME` is a valid currency name
* `USER_ID` is a valid user identity string
* `PUBLIC_KEY` is a valid public key (base58 format)
* `BLOCK_UID` refers to a [block unique ID], and represents a time reference.
* `SIGNATURE` is a signature
So the identity issuance is the act of saying:
> « I attest, today, that this identity refers to me. »
##### Example
A valid identity:
```yml
Version: 10
Type: Identity
Currency: beta_brousouf
Issuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
UniqueID: lolcat
Timestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
```
### Revocation
#### Definition
An identity revocation is the act, for a given public key's owner, to revoke an identity they created for representing themself. Doing a self-revocation is exactly like saying:
> « This identity I created makes no sense anymore. It has to be definitively locked. »
#### Use case
Its goal is only to inform that a created identity was either made by mistake, or contained a mistake, may have been compromised (private key stolen, lost...), or because for some reason you want to make yourself another identity.
#### Format
A revocation is a *signed* document gathering the identity informations to revoke:
```yml
Version: 10
Type: Revocation
Currency: CURRENCY_NAME
Issuer: PUBLIC_KEY
IdtyUniqueID: USER_ID
IdtyTimestamp: BLOCK_UID
IdtySignature: IDTY_SIGNATURE
REVOCATION_SIGNATURE
```
Where:
* `REVOCATION_SIGNATURE` is the signature over the document, `REVOCATION_SIGNATURE` excluded.
#### Example
If we have the following identity:
```yml
Version: 10
Type: Identity
Currency: beta_brousouf
Issuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
UniqueID: lolcat
Timestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
```
A valid revocation could be:
```yml
Version: 10
Type: Revocation
Currency: beta_brousouf
Issuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
IdtyUniqueID: lolcat
IdtyTimestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk
```
> The revocation actually *contains* the identity, so a program receiving a Revocation can extract the Identity and check the validity of its signature before processing the Revocation document.
### Certification
#### Definition
A *certification* in UCP refers to the document *certifying* that someone else's identity is to consider as the unique reference to a living individual.
#### Format
A certification has the following format:
```yml
Version: 10
Type: Certification
Currency: CURRENCY_NAME
Issuer: PUBLIC_KEY
IdtyIssuer: IDTY_ISSUER
IdtyUniqueID: USER_ID
IdtyTimestamp: BLOCK_UID
IdtySignature: IDTY_SIGNATURE
CertTimestamp: BLOCK_UID
CERTIFIER_SIGNATURE
```
Where:
* `BLOCK_UID` refers to a block unique ID.
* `CERTIFIER_SIGNATURE` is the signature of the *certifier*.
#### Inline format
Certifications may exist in an *inline format*, which describes the certification in a simple line. Here is the general structure:
PUBKEY_FROM:PUBKEY_TO:BLOCK_ID:SIGNATURE
Where:
* `PUBKEY_FROM` is the certification public key
* `PUBKEY_TO` is the public key whose identity is being certified
* `BLOCK_ID` is the certification time reference
* `SIGNATURE` is the certification signature
> Note: BLOCK_UID is not required in the inline format, since this format aims at being used in the context of a blockchain, where block_uid can be deduced.
#### Example
If we have the following complete self-certification:
```yml
Version: 10
Type: Identity
Currency: beta_brousouf
Issuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
UniqueID: lolcat
Timestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
```
A valid certification could be:
```yml
Version: 10
Type: Certification
Currency: beta_brousouf
Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
IdtyIssuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
IdtyUniqueID: lolcat
IdtyTimestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
CertTimestamp: 36-1076F10A7397715D2BEE82579861999EA1F274AC
SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk
```
### Membership
In UCP, a member is represented by a public key they are supposed to own. To be integrated in a WoT, the newcomer owner of the key *has to express their will* to integrate the WoT.
This step is done by issuing the following document:
```yml
Version: VERSION
Type: Membership
Currency: CURRENCY_NAME
Issuer: ISSUER
Block: M_BLOCK_UID
Membership: MEMBERSHIP_TYPE
UserID: USER_ID
CertTS: BLOCK_UID
```
followed by a signature of the `Issuer`.
#### Fields details
Field | Description
----- | -----------
`Version` | Denotes the current structure version.
`Type` | Type of the document.
`Currency` | Contains the name of the currency.
`Issuer` | The public key of the issuer.
`Block` | Block number and hash. Value is used to target a blockchain and precise time reference for membership's time validity.
`Membership` | Membership message. Value is either `IN` or `OUT` to express whether a member wishes to opt-in or opt-out the community.
`UserID` | Identity to use for this public key
`CertTS` | Identity's block UID
#### Validity
A [Membership](#membership) is to be considered having valid format if:
* `Version` equals `2`
* `Type` equals `Membership` value.
* `Currency` is a valid currency name
* `Issuer` is a public key
* `Membership` matches either `IN` or `OUT` value
* `Block` starts with an integer value, followed by a dash and an uppercased SHA1 string
* `UserID` is a non-empty string
* `CertTS` is a valid block UID
### Transaction
#### Definition
Transaction is the support of money: it allows to materialize coins' ownership.
#### Money ownership
Money ownership **IS NOT** limited to members of the Community. Any owner (an individual or an organization) of a public key may own money: it only requires the key to match `Ouputs` of a transaction.
#### Transfering money
Obviously, coins a sender does not own CANNOT be sent by them. That is why a transaction refers to other transactions, to prove that the sender actually owns the coins they want to send.
#### Format
A transaction is defined by the following format:
```yml
Version: VERSION
Type: Transaction
Currency: CURRENCY_NAME
Blockstamp: BLOCK_UID
Locktime: INTEGER
Issuers:
PUBLIC_KEY
...
Inputs:
INPUT
...
Unlocks:
UNLOCK
...
Outputs:
AMOUNT:BASE:CONDITIONS
...
Comment: COMMENT
SIGNATURES
...
```
Here is a description of each field:
Field | Description
----- | -----------
`Version` | denotes the current structure version
`Type` | type of the document
`Currency` | contains the name of the currency
`Blockstamp` | a block reference as timestamp of the transaction
`Locktime` | waiting delay to be included in the blockchain
`Issuers` | a list of public keys
`Inputs` | a list of money sources
`Unlocks` | a list of values justifying inputs consumption
`Outputs` | a list of amounts and conditions to unlock them
`Comment` | a comment to write on the transaction
#### Validity
A Transaction structure is considered *valid* if:
* Field `Version` equals `2` or `3`.
* Field `Type` equals `Transaction`.
* Field `Currency` is not empty.
* Field `Blockstamp` is a block UID
* Field `Locktime` is an integer
* Field `Issuers` is a multiline field whose lines are public keys.
* Field `Inputs` is a multiline field whose lines match either:
* `AMOUNT:BASE:D:PUBLIC_KEY:BLOCK_ID` format
* `AMOUNT:BASE:T:T_HASH:T_INDEX` format
* Field `Unlocks` is a multiline field whose lines follow `INDEX:UL_CONDITIONS` format:
* `IN_INDEX` must be an integer value
* `UL_CONDITIONS` must be a valid [Input Condition](#input-condition)
* Field `Outputs` is a multiline field whose lines follow `AMOUNT:BASE:CONDITIONS` format:
* `AMOUNT` must be an integer value
* `BASE` must be an integer value
* `CONDITIONS` must be a valid [Output Condition](#output-condition)
* Field `Comment` is a string of maximum 255 characters, exclusively composed of alphanumeric characters, space, `-`, `_`, `:`, `/`, `;`, `*`, `[`, `]`, `(`, `)`, `?`, `!`, `^`, `+`, `=`, `@`, `&`, `~`, `#`, `{`, `}`, `|`, `\`, `<`, `>`, `%`, `.`. Must be present even if empty.
#### Input condition
It is a suite of values each separated by a space "` `". Values must be either a `SIG(INDEX)` or `HXH(INTEGER)`.
If no values are provided, the valid input condition is an empty string.
##### Input condition examples
* `SIG(0)`
* `XHX(73856837)`
* `SIG(0) SIG(2) XHX(343)`
* `SIG(0) SIG(2) SIG(4) SIG(6)`
#### Output condition
It follows a machine-readable BNF grammar composed of
* `(` and `)` characters
* `&&` and `||` operators
* `SIG(PUBLIC_KEY)`, `XHX(SHA256_HASH)`, `CLTV(INTEGER)`, `CSV(INTEGER)` functions
* ` ` space
**An empty condition or a condition fully composed of spaces is considered an invalid output condition**.
Also, the maximum length of a condition is 1000 characters. // TODO: OK?
##### Output condition examples
* `SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd)`
* `(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) && XHX(309BC5E644F797F53E5A2065EAF38A173437F2E6))`
* `(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) && CSV(3600))`
* `(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) && (CLTV(1489677041) || CSV(3600)))`
* `(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) || (SIG(DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV) && XHX(309BC5E644F797F53E5A2065EAF38A173437F2E6)))`
#### Condition matching
Considering a transaction `TX` consuming some money: each unlock line `UNLOCK` of TX tries to unlock the input `INPUT = TX.Inputs[IN_INDEX]` refering to an ouput `Output` whose value is:
If `INPUT.TYPE = 'T'`:
Output = TRANSACTIONS_IN_BLOCKCHAIN_OR_IN_LOCAL_BLOCK[T_HASH].OUPUTS[T_INDEX]
If `INPUT.TYPE = 'D'`:
Output = BLOCKCHAIN[BLOCK_ID].Dividend[PUBLIC_KEY]
Let's name:
* `CONDITIONS` the conditions of a given output `Output`
* `NB_FUNCTIONS` the number of functions present in `CONDITIONS` (read from left to right)
* `NB_PARAMETERS` the number of parameters present in `UNLOCK`, each separated by a space (read from left to right)
Then, an `UNLOCK` line is considered *successful* if:
* `Output` exists
* `NB_PARAMETERS <= NB_FUNCTIONS`
* Each `UNLOCK` parameter returns TRUE
* `CONDITIONS` evaluated globally with `(`, `)`, `&&`, `||` and its locking functions returns TRUE
##### Example 1
TX1:
```yml
Outputs:
50:2:XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)
```
Is resolved by TX2:
```yml
Unlocks:
0:XHX(1872767826647264)
```
Because `XHX(1872767826647264) = 8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB` (this will be explained in the next section).
##### Example 2
TX1:
```yml
Outputs:
50:2:SIG(DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo)
```
Is resolved by TX2:
```yml
Issuers:
HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
[...]
Unlocks:
0:SIG(1)
```
Because `SIG(1)` refers to the signature `DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo`, considering that signature `DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo` is good over TX2.
#### Locking and unlocking functions
The *locking* functions are present under `Outputs` field.
The *unlocking* functions are present under `Unlocks` field.
##### SIG function
###### Definition
Lock:
SIG(PUBKEY_A)
Unlock:
SIG(INDEX)
###### Condition
Lock `SIG(PUBKEY_A)` returns TRUE if it exists a parameter `SIG(INDEX)` returning TRUE where `Issuers[INDEX] == PUBKEY_A`.
Unlock `SIG(INDEX)` returns TRUE if `Signatures[INDEX]` is a valid valid signature of TX against `Issuers[INDEX]` public key.
###### Description
This function is a control over the signature.
* in an `Output` of a transaction TX1, `SIG(PUBKEY_A)` requires from a future transaction TX2 unlocking the output to give as parameter a valid signature of TX2 by `PUBKEY_A`
* if TX2 does not give `SIG(INDEX)` parameter as [matching parameter](#condition-matching), the condition fails
* if TX2's `Issuers[INDEX]` does not equal `PUBKEY_A`, the condition fails
* if TX2's `SIG(INDEX)` does not return TRUE, the condition fails
* in an `Unlock` of TX2, `SIG(INDEX)` returns TRUE if `Signatures[INDEX]` is a valid signature of TX2 against `Issuers[INDEX]`
So if we have, in TX1:
```yml
Version: 10
Type: Transaction
[...]
Outputs
25:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
```
Then the `25` units can be spent *exclusively* in a future transaction TX2 which looks like:
```yml
Version: 10
Type: Transaction
[...]
Issuers:
BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g
Inputs:
25:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0
Unlocks:
0:SIG(0)
```
Where:
* `SIG(0)` refers to the signature of `Issuers[0]`, here `BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g`
* `6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3` is the hash of TX1.
The necessary condition `SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)` is matched here if **both**:
* the sufficient proof `SIG(0)` is a valid signature of TX2 against `Issuers[0]` public key
* `Issuers[0] = BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g`
##### XHX function
###### Definition
Lock:
XHX(HASH)
Unlock:
XHX(PASSWORD)
###### Condition
`XHX(HASH)` returns true if it exists a parameter `XHX(PASSWORD)` where `SHA256(PASSWORD) = HASH`.
`XHX(PASSWORD)` returns true if it exists a locking function `XHX(HASH)` where `SHA256(PASSWORD) = HASH` in the conditions.
###### Description
This function is a password control.
So if we have, in TX1:
```yml
Version: 10
Type: Transaction
[...]
Outputs
25:2:XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)
```
Then the `25` units can be spent *exclusively* in a future transaction TX2 which looks like:
```yml
Version: 10
Type: Transaction
[...]
Issuers:
BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g
Inputs:
55:1:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0
Unlocks:
0:XHX(1872767826647264)
```
Where:
* `6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3` is the hash of TX1.
Then `XHX(PASSWORD)` returns TRUE if it exists `XHX(HASH)` in the output's conditions of TX1, with the relation `SHA256(PASSWORD) = HASH`.
Here `XHX(1872767826647264)` returns TRUE if it exists `XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)` in the output's conditions of TX1, as it matches the link `SHA256(1872767826647264) = 8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB`.
#### Example 1
Key `HsLShA` sending 25 coins to key `BYfWYF` using 1 source transaction written in block #3.
```yml
Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
Locktime: 0
Issuers:
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
Inputs:
30:0:T:8361C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:3
Unlocks:
0:SIG(0)
Outputs:
25:0:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
5:0:SIG(HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY)
Comment: First transaction
```
Signatures (fake here):
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
#### Example 2
Key `HsLShA` sending 30 coins (base 2) to key `BYfWYF` using 2 sources transaction written in blocks #65 and #77 + 1 UD from block #88.
```yml
Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
Locktime: 0
Issuers:
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
Inputs:
6:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0
20:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:10
40:1:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:88
Unlocks:
0:SIG(0)
1:SIG(0)
2:SIG(0)
Outputs:
30:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
Comment:
```
Signatures (fake here):
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
#### Example 3
Key `HsLShA`, `CYYjHs` and `9WYHTa` sending 235 coins to key `BYfWYF` using 4 sources transaction + 2 UD from same block #46.
```yml
Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
Locktime: 0
Issuers:
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp
9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB
Inputs:
40:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
70:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
20:2:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46
70:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
20:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
15:2:D:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:46
Unlocks:
0:SIG(0)
1:XHX(7665798292)
2:SIG(0)
3:SIG(0) SIG(2)
4:SIG(0) SIG(1) SIG(2)
5:SIG(2)
Outputs:
120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85))
Comment: -----@@@----- (why not this comment?)
```
Signatures (fakes here):
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX
2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk
##### CLTV function
###### Definition
Lock:
CLVT(TIMESTAMP)
Unlock: no unlocking function.
###### Condition
`CLVT(TIMESTAMP)` returns true if and only if the transaction including block's `MedianTime >= TIMESTAMP`.
###### Description
This function locks an output in the future, which will be unlocked at a given date.
So if we have, in TX1:
```yml
Version: 10
Type: Transaction
[...]
Outputs
25:2:CLTV(1489677041)
```
So here, the `25` units can be spent *exclusively* in a block whose `MedianTime >= 1489677041`
`CLTV`'s parameter must be an integer with a length between `1` and `10` chars.
##### CSV function
###### Definition
Lock:
CSV(DELAY)
Unlock: no unlocking function.
We define `TxTime` as the `MedianTime` of the block referenced by the transaction's `Blockstamp` field.
###### Condition
`CSV(DELAY)` returns true if and only if the transaction including block's `MedianTime - TxTime >= DELAY`.
###### Description
This function locks an output in the future, which will be unlocked after the given amount of time has elapsed.
So if we have, in TX1:
```yml
Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
[...]
Outputs
25:2:CSV(3600)
```
Then the `25` units can be spent *exclusively* in a block whose `MedianTime - TxTime >= 3600`.
`CSV`'s parameter must be an integer with a length between `1` and `8` chars.
#### Compact format
A transaction may be described with a more compact format, to be used in a [Block](#block) document. The general format is:
```yml
TX:VERSION:NB_ISSUERS:NB_INPUTS:NB_UNLOCKS:NB_OUTPUTS:HAS_COMMENT:LOCKTIME
BLOCKSTAMP
PUBLIC_KEY
...
INPUT
...
UNLOCK
...
OUTPUT
...
COMMENT
SIGNATURE
...
```
Here is an example compacting [example 2](#example-2) from above:
```yml
TX:10:1:3:1:0:0
204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
6:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0
20:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:10
40:1:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:88
0:SIG(0)
1:SIG(0)
2:SIG(0)
30:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
```
Here is an example compacting [example 3](#example-3) from above:
```yml
TX:10:3:6:3:1:0
204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp
9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB
40:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
70:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
20:2D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46
70:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
20:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
15:2:D:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:46
0:SIG(0)
1:XHX(7665798292)
2:SIG(0)
3:SIG(0) SIG(2)
4:SIG(0) SIG(1) SIG(2)
5:SIG(2)
120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85))
-----@@@----- (why not this comment?)
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX
2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk
```
### Block
A Block is a document gathering both:
* [Public key](#publickey) data in order to build a Web Of Trust (WoT) representation
* [Transaction](#transaction) data to identify money units & ownership
but also other informations like:
* time reference (calendar time)
* UD value for money issuance
#### Structure
```yml
Version: VERSION
Type: Block
Currency: CURRENCY
Number: BLOCK_ID
PoWMin: NUMBER_OF_ZEROS
Time: GENERATED_ON
MedianTime: MEDIAN_DATE
UniversalDividend: DIVIDEND_AMOUNT
UnitBase: UNIT_BASE
Issuer: ISSUER_KEY
IssuersFrame: ISSUERS_FRAME
IssuersFrameVar: ISSUERS_FRAME_VAR
DifferentIssuersCount: ISSUER_KEY
PreviousHash: PREVIOUS_HASH
PreviousIssuer: PREVIOUS_ISSUER_KEY
Parameters: PARAMETERS
MembersCount: WOT_MEM_COUNT
Identities:
PUBLIC_KEY:SIGNATURE:I_BLOCK_UID:USER_ID
...
Joiners:
PUBLIC_KEY:SIGNATURE:M_BLOCK_UID:I_BLOCK_UID:USER_ID
...
Actives:
PUBLIC_KEY:SIGNATURE:M_BLOCK_UID:I_BLOCK_UID:USER_ID
...
Leavers:
PUBLIC_KEY:SIGNATURE:M_BLOCK_UID:I_BLOCK_UID:USER_ID
...
Revoked:
PUBLIC_KEY:SIGNATURE
...
Excluded:
PUBLIC_KEY
...
Certifications:
PUBKEY_FROM:PUBKEY_TO:BLOCK_ID:SIGNATURE
...
Transactions:
COMPACT_TRANSACTION
...
InnerHash: BLOCK_HASH
Nonce: NONCE
BOTTOM_SIGNATURE
```
Field | Data | Mandatory?
--------------------- | ------------------------------------------------- | ------------
Version | The document version | Always
Type | The document type | Always
Currency | The currency name | Always
Number | The block number | Always
PoWMin | The current minimum PoW difficulty | Always
Time | Time of generation | Always
MedianTime | Median date | Always
UniversalDividend | Universal Dividend amount | **Optional**
UnitBase | Universal Dividend unit base (power of 10) | Always in V3, **Optional** in V2
Issuer | This block's issuer's public key | Always
IssuersFrame | The size of the frame to look for issuers | Always
IssuersFrameVar | A counter to increment/decrement IssuersFrame | Always
DifferentIssuersCount | The count of unique issuers of blocks | Always
PreviousHash | Previous block fingerprint (SHA256) | from Block#1
PreviousIssuer | Previous block issuer's public key | from Block#1
Parameters | Currency parameters. | **Block#0 only**
MembersCount | Number of members in the WoT, this block included | Always
Identities | New identities in the WoT | Always
Joiners | `IN` memberships | Always
Actives | `IN` memberships (renewal) | Always
Leavers | `OUT` memberships | Always
Revoked | Revocation documents | Always
Excluded | Exluded members' public key | Always
Transactions | A list of compact transactions | Always
InnerHash | The hash value of the block's inner content | Always
Nonce | An arbitrary nonce value | Always
BlockHash | Hash from `InnerHash: ` to `SIGNATURE` | Virtual
BlockSize | Hash from `InnerHash: ` to `SIGNATURE` | Virtual
##### BlockSize
The block size is defined as the number of lines in multiline fields (`Identities`, `Joiners`, `Actives`, `Leavers`, `Revoked`, `Certifications`, `Transactions`) **except** `Excluded` field.
For example:
* 1 new identity + 1 joiner + 2 certifications = 4 lines sized block
* 1 new identity + 1 joiner + 2 certifications + 5 lines transaction = 9 lines sized block
#### Coherence
To be valid, a block must match the following rules:
##### Format
* `Version`, `Nonce`, `Number`, `PoWMin`, `Time`, `MedianTime`, `MembersCount`, `UniversalDividend`, `UnitBase`, `IssuersFrame`, `IssuersFrameVar` and `DifferentIssuersCount` are integer values
* `Currency` is a valid currency name
* `PreviousHash` is an uppercased SHA256 hash
* `Issuer` and `PreviousIssuer` are [Public keys](#publickey)
* `Identities` is a multiline field composed for each line of:
* `PUBLIC_KEY` : a [Public key](#publickey)
* `SIGNATURE` : a [Signature](#signature)
* `BLOCK_UID` : a block UID
* `USER_ID` : an identifier
* `Joiners`, `Actives` and `Leavers` are multiline fields composed for each line of:
* `PUBLIC_KEY` : a [Public key](#publickey)
* `SIGNATURE` : a [Signature](#signature)
* `M_BLOCK_UID` : a block UID
* `I_BLOCK_UID` : a block UID
* `USER_ID` : an identifier
* `Revoked` is a multiline field composed for each line of:
* `SIGNATURE` : a [Signature](#signature)
* `USER_ID` : an identifier
* `Excluded` is a multiline field composed for each line of:
* `PUBLIC_KEY` : a [Public key](#publickey)
* `Certifications` is a multiline field composed for each line of:
* `PUBKEY_FROM` : a [Public key](#publickey) doing the certification
* `PUBKEY_TO` : a [Public key](#publickey) being certified
* `BLOCK_ID` : a positive integer
* `SIGNATURE` : a [Signature](#signature) of the certification
* `Transactions` is a multiline field composed of [compact transactions](#compact-format)
* `Parameters` is a simple line field, composed of 1 float, 12 integers and 1 last float all separated by a colon `:`, and representing [currency parameters](#protocol-parameters) (a.k.a Protocol parameters, but valued for a given currency):
c:dt:ud0:sigPeriod:sigStock:sigWindow:sigValidity:sigQty:idtyWindow:msWindow:xpercent:msValidity:stepMax:medianTimeBlocks:avgGenTime:dtDiffEval:percentRot:udTime0:udReevalTime0:dtReeval
The document must be ended with a `BOTTOM_SIGNATURE` [Signature](#signature).
##### Data
* `Version` equals `6`
* `Type` equals `Block`
### Peer
UCP uses P2P networks to manage community and money data. Since only members can write to the Blockchain, it is important to have authenticated peers so newly validated blocks can be efficiently sent to them, without any ambiguity.
For that purpose, UCP defines a peering table containing, for a given node's public key:
* a currency name
* a list of endpoints to contact the node
This link is made through a document called *Peer* whose format is described below.
#### Structure
```yml
Version: VERSION
Type: Peer
Currency: CURRENCY_NAME
Issuer: NODE_PUBLICKEY
Block: BLOCK
Endpoints:
END_POINT_1
END_POINT_2
END_POINT_3
[...]
```
With the signature attached, this document certifies that this public key is owned by this server at given network endpoints.
The aggregation of all *Peer* documents is called the *peering table*, and allows to authentify addresses of all nodes identified by their public keys.
#### Fields details
Field | Description
----- | -----------
`Version` | denotes the current structure version.
`Type` | the document type.
`Currency` | contains the name of the currency.
`Issuer` | the node's public key.
`Block` | block number and hash. Value is used to target a blockchain and precise time reference.
`Endpoints` | a list of endpoints to interact with the node
`Endpoints` has a particular structure. It is made up of at least one line, with each line following this format:
PROTOCOL_NAME[ OPTIONS]
[...]
For example, the first written Duniter peering protocol is BASIC_MERKLED_API, which defines an HTTP API. An endpoint of such protocol would look like:
BASIC_MERKLED_API[ DNS][ IPv4][ IPv6] PORT
Where :
Field | Description
----- | -----------
`DNS` | is the dns name to access the node.
`IPv4` | is the IPv4 address to access the node.
`IPv6` | is the IPv6 address to access the node.
`PORT` | is the port of the address to access the node.
#### Coherence
To be valid, a peer document must match the following rules:
##### Format
* `Version` equals `2` or `3`
* `Type` equals `Peer`
* `Currency` is a valid currency name
* `PublicKey` is a [Public key](#publickey)
* `Endpoints` is a multiline field
The document must be ended with a `BOTTOM_SIGNATURE` [Signature](#signature).
#### Example
```yml
Version: 10
Type: Peer
Currency: beta_brousouf
PublicKey: HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
Block: 8-1922C324ABC4AF7EF7656734A31F5197888DDD52
Endpoints:
BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9001
BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9002
OTHER_PROTOCOL 88.77.66.55 9001
```
## Variables
### Protocol parameters
Parameter | Goal
----------- | ----
c | The %growth of the UD every `[dt]` period
dt | Time period between two UD.
dtReeval | Time period between two re-evaluation of the UD.
ud0 | UD(0), i.e. initial Universal Dividend
udTime0 | Time of first UD.
udReevalTime0 | Time of first reevaluation of the UD.
sigPeriod | Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero.
msPeriod | Minimum delay between 2 memberships of a same issuer, in seconds. Must be positive or zero.
sigReplay | Minimum delay between 2 certifications of a same issuer to a same receiver, in seconds. Equals to `msPeriod`.
sigStock | Maximum quantity of active certifications made by member.
sigWindow | Maximum delay a certification can wait before being expired for non-writing.
sigValidity | Maximum age of an active signature (in seconds)
sigQty | Minimum quantity of signatures to be part of the WoT
idtyWindow | Maximum delay an identity can wait before being expired for non-writing.
msWindow | Maximum delay a membership can wait before being expired for non-writing.
xpercent | Minimum percent of sentries to reach to match the distance rule
msValidity | Maximum age of an active membership (in seconds)
stepMax | Maximum distance between each WoT member and a newcomer
medianTimeBlocks | Number of blocks used for calculating median time.
avgGenTime | The average time for writing 1 block (wished time)
dtDiffEval | The number of blocks required to evaluate again `PoWMin` value
percentRot | The percent of previous issuers to reach for personalized difficulty
txWindow | `= 3600 * 24 * 7`. Maximum delay a transaction can wait before being expired for non-writing.
### Computed variables
Variable | Meaning
--------- | ----
members | Synonym of `members(t = now)`, `wot(t)`, `community(t)` targeting the keys whose last active (non-expired) membership is either in `Joiners` or `Actives`.
maxGenTime | `= CEIL(avgGenTime * 1.189)`
minGenTime | `= FLOOR(avgGenTime / 1.189)`
minSpeed | 1 / maxGenTime
maxSpeed | 1 / minGenTime
maxAcceleration | `= CEIL(maxGenTime * medianTimeBlocks)`
## Processing
### Block
A Block can be accepted only if it respects a set of rules, here divided in 2 parts : *local* and *global*.
#### Local validation
Local validation verifies the coherence of a well-formatted block, without any other context than the block itself.
##### Version
Rule:
HEAD.version == 10 || HEAD.version == 11
##### InnerHash
* `InnerHash` is the SHA256 hash of the whole fields from `Version:` to `\InnerHash`, the 'InnerHash' string being excluded from the hash computation.
##### Nonce
* `Nonce` value may be any zero or positive integer. This field is a special field allowing for document hash to change for proof-of-work computation.
##### Proof of work
To be valid, the BlockHash must start with a specific number of zeros. Locally, this hash must start with at least `NB_ZEROS` zeros:
REMAINDER = PowMin % 16
NB_ZEROS = (PowMin - REMAINDER) / 16
##### PreviousHash
* `PreviousHash` must be present if the value of the `Number` field is greater than `0`.
* `PreviousHash` must not be present if the value of the `Number` field is equal to `0`.
##### PreviousIssuer
* `PreviousIssuer` must be present if the value of the `Number` field is greater than `0`.
* `PreviousIssuer` must not be present if the value of the `Number` field is equal to `0`.
##### Parameters
* `Parameters` must be present if the value of the `Number` field is equal to `0`.
* `Parameters` must not be present if the value of the `Number` field is greater than `0`.
##### Universal Dividend
If HEAD.number == 0, HEAD.dividend must equal `null`.
##### UnitBase
* Block V2:
* If `UniversalDividend` field is present, `UnitBase` must be present too.
* Block V3:
* The field is always present.
* For root block, `UnitBase` must equal `0`.
##### Signature
* A block must have a valid signature over the content from `Hash: ` to `Nonce: NONCE\n`, where the associated public key is the block's `Issuer` field.
##### Dates
* A block must have its `Time` field be between [`MedianTime` ; `MedianTime` + `maxAcceleration`].
* Root block's `Time` & `MedianTime` must be equal.
##### Identities
A block cannot contain identities whose signature does not match the identity's content
##### Memberships (Joiners, Actives, Leavers)
A block cannot contain memberships whose signature does not match the membership's content
##### Revoked
A block cannot contain revocations whose signature does not match the revocation's content
##### Transactions
* A transaction in compact format cannot measure more than 100 lines
* A transaction must have at least 1 source
* A transaction cannot have `SIG(INDEX)` unlocks with `INDEX >= ` issuers count.
* A transaction **must** have signatures matching its content **for each issuer**
* A transaction's version must be equal to `3`
* Signatures count must be the same as issuers count
* Signatures are ordered by issuer
* Signatures are made over the transaction's content, signatures excepted
##### INDEX GENERATION
##### Identities
Each identity produces 2 new entries:
IINDEX (
op = 'CREATE'
uid = USER_ID
pub = PUBLIC_KEY
created_on = BLOCK_UID
written_on = BLOCKSTAMP
member = true
wasMember = true
kick = false
)
MINDEX (
op = 'CREATE'
pub = PUBLIC_KEY
created_on = BLOCK_UID
written_on = BLOCKSTAMP
expired_on = 0
expires_on = MedianTime + msValidity
revokes_on = MedianTime + msValidity*2
chainable_on = MedianTime + msPeriod
type = 'JOIN'
revoked_on = null
leaving = false
)
##### Joiners
Each join whose `PUBLIC_KEY` **does not match** a local MINDEX `CREATE, PUBLIC_KEY` produces 2 new entries:
IINDEX (
op = 'UPDATE'
uid = null
pub = PUBLIC_KEY
created_on = null
written_on = BLOCKSTAMP
member = true
wasMember = null
kick = null
)
MINDEX (
op = 'UPDATE'
pub = PUBLIC_KEY
created_on = BLOCK_UID
written_on = BLOCKSTAMP
expired_on = 0
expires_on = MedianTime + msValidity
revokes_on = MedianTime + msValidity*2
chainable_on = MedianTime + msPeriod
type = 'JOIN'
revoked_on = null
leaving = null
)
##### Actives
Each active produces 1 new entry:
MINDEX (
op = 'UPDATE'
pub = PUBLIC_KEY
created_on = BLOCK_UID
written_on = BLOCKSTAMP
expires_on = MedianTime + msValidity
revokes_on = MedianTime + msValidity*2
chainable_on = MedianTime + msPeriod
type = 'RENEW'
revoked_on = null
leaving = null
)
##### Leavers
Each leaver produces 1 new entry:
MINDEX (
op = 'UPDATE'
pub = PUBLIC_KEY
created_on = BLOCK_UID
written_on = BLOCKSTAMP
type = 'LEAVE'
expires_on = null
revokes_on = null
revoked_on = null
leaving = true
)
##### Revoked
Each revocation produces 1 new entry:
MINDEX (
op = 'UPDATE'
pub = PUBLIC_KEY
created_on = BLOCK_UID
written_on = BLOCKSTAMP
type = 'REV'
expires_on = null
revokes_on = null
revoked_on = MedianTime
revocation = REVOCATION_SIG
leaving = false
)
##### Excluded
Each exclusion produces 1 new entry:
IINDEX (
op = 'UPDATE'
uid = null
pub = PUBLIC_KEY
created_on = null
written_on = BLOCKSTAMP
member = false
wasMember = null
kick = false
)
##### Certifications
Each certification produces 1 new entry:
CINDEX (
op = 'CREATE'
issuer = PUBKEY_FROM
receiver = PUBKEY_TO
created_on = BLOCK_ID
written_on = BLOCKSTAMP
sig = SIGNATURE
expires_on = MedianTime + sigValidity
chainable_on = MedianTime + sigPeriod
replayable_on = MedianTime + sigReplay
expired_on = 0
)
##### Sources
Each transaction input produces 1 new entry:
SINDEX (
op = 'UPDATE'
tx = TRANSACTION_HASH
identifier = INPUT_IDENTIFIER
pos = INPUT_INDEX
created_on = TX_BLOCKSTAMP
written_on = BLOCKSTAMP
amount = INPUT_AMOUNT
base = INPUT_BASE
conditions = null
consumed = true
)
Each transaction output produces 1 new entry:
SINDEX (
op = 'CREATE'
tx = TRANSACTION_HASH
identifier = TRANSACTION_HASH
pos = OUTPUT_INDEX_IN_TRANSACTION
written_on = BLOCKSTAMP
written_time = MedianTime
amount = OUTPUT_AMOUNT
base = OUTPUT_BASE
locktime = LOCKTIME
conditions = OUTPUT_CONDITIONS
consumed = false
)
##### INDEX RULES
###### UserID and PublicKey unicity
* The local IINDEX has a unicity constraint on `USER_ID`.
* The local IINDEX has a unicity constraint on `PUBLIC_KEY`.
* Each local IINDEX `op = 'CREATE'` operation must match a single local MINDEX `op = 'CREATE', pub = PUBLIC_KEY` operation.
> Functionally: UserID and public key must be unique in a block, an each new identity must have an opt-in document attached.
###### Membership unicity
* The local MINDEX has a unicity constraint on `PUBLIC_KEY`
> Functionally: a user has only 1 status change allowed per block.
###### Revocation implies exclusion
* Each local MINDEX ̀`op = 'UPDATE', revoked_on != null` operations must match a single local IINDEX `op = 'UPDATE', pub = PUBLIC_KEY, member = false` operation.
> Functionally: a revoked member must be immediately excluded.
###### Certifications
* The local CINDEX has a unicity constraint on `PUBKEY_FROM, PUBKEY_TO`
* The local CINDEX has a unicity constraint on `PUBKEY_FROM`, except for block#0
* The local CINDEX must not match a MINDEX operation on `PUBLIC_KEY = PUBKEY_FROM, member = false` or `PUBLIC_KEY = PUBKEY_FROM, leaving = true`
> Functionally:
>
> * a block cannot have 2 identical certifications (A -> B)
> * a block cannot have 2 certifications from a same public key, except in block#0
> * a block cannot have a certification to a leaver or an excluded
###### Sources
* The local SINDEX has a unicity constraint on `UPDATE, IDENTIFIER, POS`
* The local SINDEX has a unicity constraint on `CREATE, IDENTIFIER, POS`
> Functionally:
> * a same source cannot be consumed twice by the block
> * a same output cannot be produced twice by block
>
> But a source can be both created and consumed in the same block, so a *chain of transactions* can be stored at once.
##### Double-spending control
Definitions:
For each SINDEX unique `tx`:
* **inputs** are the SINDEX row matching `UPDATE, tx`
* **outputs** are the SINDEX row matching `CREATE, tx`
> Functionally: we gather the sources for each transaction, in order to check them.
###### CommonBase
Each input has an `InputBase`, and each output has an `OutputBase`. These bases are to be called `AmountBase`.
The `CommonBase` is the lowest base value among all `AmountBase` of the transaction.
For any amount comparison, the respective amounts must be translated into `CommonBase` using the following rule:
```
AMOUNT(CommonBase) = AMOUNT(AmountBase) x POW(10, AmountBase - CommonBase)
```
So if a transaction only carries amounts with the same `AmountBase`, no conversion is required. But if a transaction carries:
* input_0 of value 45 with `AmountBase = 5`
* input_1 of value 75 with `AmountBase = 5`
* input_2 of value 3 with `AmountBase = 6`
* output_0 of value 15 with `AmountBase = 6`
Then the output value has to be converted before being compared:
```
CommonBase = 5
output_0(5) = output_0(6) x POW(10, 6 - 5)
output_0(5) = output_0(6) x POW(10, 1)
output_0(5) = output_0(6) x 10
output_0(5) = 15 x 10
output_0(5) = 150
input_0(5) = input_0(5)
input_0(5) = 45
input_1(5) = input_1(5)
input_1(5) = 75
input_2(5) = input_2(6) x POW(10, 6 - 5)
input_2(5) = input_2(6) x POW(10, 1)
input_2(5) = input_2(6) x 10
input_2(5) = 3 x 10
input_2(5) = 30
```
The equality of inputs and outputs is then verified because:
```
output_0(5) = 150
input_0(5) = 45
input_1(5) = 75
input_2(5) = 30
output_0(5) = input_0(5) + input_1(5) + input_2(5)
150 = 45 + 75 + 30
TRUE
```
###### Amounts
* *Def.*: `InputBaseSum` is the sum of amounts with the same `InputBase`.
* *Def.*: `OutputBaseSum` is the sum of amounts with the same `OutputBase`.
* *Def.*: `BaseDelta = OutputBaseSum - InputBaseSum`, expressed in `CommonBase`
* *Rule*: For each `OutputBase`:
* if `BaseDelta > 0`, then it must be inferior or equal to the sum of all preceding `BaseDelta`
* *Rule*: The sum of all inputs in `CommonBase` must equal the sum of all outputs in `CommonBase`
> Functionally: we cannot create nor lose money through transactions. We can only transfer coins we own.
> Functionally: also, we cannot convert a superiod unit base into a lower one.
##### Transactions chaining max depth
FUNCTION `getTransactionDepth(txHash, LOCAL_DEPTH)`:
INPUTS = LOCAL_SINDEX[op='UPDATE',tx=txHash]
DEPTH = LOCAL_DEPTH
FOR EACH `INPUT` OF `INPUTS`
CONSUMED = LOCAL_SINDEX[op='CREATE',identifier=INPUT.identifier,pos=INPUT.pos]
IF (CONSUMED != NULL)
IF (LOCAL_DEPTH < 5)
DEPTH = MAX(DEPTH, getTransactionDepth(CONSUMED.tx, LOCAL_DEPTH +1)
ELSE
DEPTH++
END_IF
END_IF
END_FOR
RETURN DEPTH
END_FUNCTION
Then:
maxTxChainingDepth = 0
For each `TX_HASH` of `UNIQ(PICK(LOCAL_SINDEX, 'tx))`:
maxTxChainingDepth = MAX(maxTxChainingDepth, getTransactionDepth(TX_HASH, 0))
Rule:
maxTxChainingDepth <= 5
#### Global
Global validation verifies the coherence of a locally-validated block, in the context of the whole blockchain, including the block.
##### INDEX GENERATION
###### Definitions
Here are some references that will be used in next sections.
BINDEX references:
* *HEAD*: the BINDEX top entry (generated for incoming block, precisely)
* *HEAD~1*: the BINDEX 1<sup>st</sup> entry before HEAD (= entry where `ENTRY.number = HEAD.number - 1`)
* *HEAD~n*: the BINDEX n<sup>th</sup> entry before HEAD (= entry where `ENTRY.number = HEAD.number - n`)
* *HEAD~n[field=value, ...]*: the BINDEX entry at *HEAD~n* if it fulfills the condition, null otherwise
* *HEAD~n..m[field=value, ...]*: the BINDEX entries between *HEAD~n* and *HEAD~m*, included, where each entry fulfills the condition
* *HEAD~n.property*: get a BINDEX entry property. Ex.: `HEAD~1.hash` looks at the hash of the entry preceding HEAD.
* *(HEAD~n..m).property*: get all the values of *property* in BINDEX for entries between *HEAD~n* and *HEAD~m*, included.
* *(HEAD~<n>..<m>).property*: same, but <n> and <m> are variables to be computed
Function references:
* *COUNT* returns the number of values in a list of values
* *AVG* computes the average value in a list of values, floor rounded.
* *MEDIAN* computes the median value in a list of values
* *MAX* computes the maximum value in a list of values
> If values count is even, the median is computed over the 2 centered values by an arithmetical median on them, *NOT* rounded.
* *UNIQ* returns a list of the unique values in a list of values
* *PICK* returns a list of the values by picking a particular property on each record
* *INTEGER_PART* return the integer part of a number
* *FIRST* return the first element in a list of values matching the given condition
* *REDUCE* merges a set of elements into a single one, by extending the non-null properties from each record into the resulting record.
* *REDUCE_BY* merges a set of elements into a new set, where each new element is the reduction of the first set sharing a given key.
* *CONCAT* concatenates two sets of elements into a new one
> If there is no elements, all its properties are `null`.
* *NUMBER* get the number part of blockstamp
* *HASH* get the hash part of blockstamp
###### HEAD
The block produces 1 new entry:
BINDEX (
version = Version
size = BlockSize
hash = BlockHash
issuer = Issuer
time = Time
number = null
currency = null
previousHash = null
previousIssuer = null
membersCount = null
issuersCount = null
issuersFrame = null
issuersFrameVar = null
issuerDiff = null
avgBlockSize = null
medianTime = null
dividend = null
mass = null
massReeval = null
unitBase = null
powMin = PowMin
udTime = null
diffTime = null
speed = null
)
This entry will be refered to as *HEAD* in the following sections, and is added on the top of the BINDEX.
###### BR_G01 - HEAD.number
If HEAD~1 is defined:
HEAD.number = HEAD~1.number + 1
Else:
HEAD.number = 0
###### BR_G02 - HEAD.previousHash
If `HEAD.number > 0`:
HEAD.previousHash = HEAD~1.hash
Else:
HEAD.previousHash = null
###### BR_G99 - HEAD.currency
If `HEAD.number > 0`:
HEAD.currency = HEAD~1.currency
Else:
HEAD.currency = null
###### BR_G03 - HEAD.previousIssuer
If `HEAD.number > 0`:
HEAD.previousIssuer = HEAD~1.issuer
Else:
HEAD.previousIssuer = null
###### BR_G100 - HEAD.issuerIsMember
If `HEAD.number > 0`:
HEAD.issuerIsMember = REDUCE(GLOBAL_IINDEX[pub=HEAD.issuer]).member
Else:
HEAD.issuerIsMember = REDUCE(LOCAL_IINDEX[pub=HEAD.issuer]).member
###### BR_G04 - HEAD.issuersCount
If `HEAD.number == 0`:
HEAD.issuersCount = 0
Else:
HEAD.issuersCount = COUNT(UNIQ((HEAD~1..<HEAD~1.issuersFrame>).issuer))
###### BR_G05 - HEAD.issuersFrame
If `HEAD.number == 0`:
HEAD.issuersFrame = 1
Else if `HEAD~1.issuersFrameVar > 0`:
HEAD.issuersFrame = HEAD~1.issuersFrame + 1
Else if `HEAD~1.issuersFrameVar < 0`:
HEAD.issuersFrame = HEAD~1.issuersFrame - 1
Else:
HEAD.issuersFrame = HEAD~1.issuersFrame
###### BR_G06 - HEAD.issuersFrameVar
If `HEAD.number == 0`:
HEAD.issuersFrameVar = 0
Else if `HEAD~1.issuersFrameVar > 0`:
HEAD.issuersFrameVar = HEAD~1.issuersFrameVar + 5*(HEAD.issuersCount - HEAD~1.issuersCount) - 1
Else if `HEAD~1.issuersFrameVar < 0`:
HEAD.issuersFrameVar = HEAD~1.issuersFrameVar + 5*(HEAD.issuersCount - HEAD~1.issuersCount) + 1
Else:
HEAD.issuersFrameVar = HEAD~1.issuersFrameVar + 5*(HEAD.issuersCount - HEAD~1.issuersCount)
###### BR_G07 - HEAD.avgBlockSize
HEAD.avgBlockSize = AVG((HEAD~1..<HEAD.issuersCount>).size)
###### BR_G08 - HEAD.medianTime
If `HEAD.number > 0`:
HEAD.medianTime = MEDIAN((HEAD~1..<MIN(medianTimeBlocks, HEAD.number)>).time)
Else:
HEAD.medianTime = HEAD.time
###### BR_G09 - HEAD.diffNumber
If `HEAD.number == 0`:
HEAD.diffNumber = HEAD.number + dtDiffEval
Else if `HEAD~1.diffNumber <= HEAD.number`:
HEAD.diffNumber = HEAD~1.diffNumber + dtDiffEval
Else:
HEAD.diffNumber = HEAD~1.diffNumber
###### BR_G10 - HEAD.membersCount
If `HEAD.number == 0`:
HEAD.membersCount = COUNT(LOCAL_IINDEX[member=true])
Else:
HEAD.membersCount = HEAD~1.membersCount + COUNT(LOCAL_IINDEX[member=true]) - COUNT(LOCAL_IINDEX[member=false])
###### BR_G11 - HEAD.udTime and HEAD.udReevalTime
If `HEAD.number == 0`:
HEAD.udTime = udTime0
Else if `HEAD~1.udTime <= HEAD.medianTime`:
HEAD.udTime = HEAD~1.udTime + dt
Else:
HEAD.udTime = HEAD~1.udTime
EndIf
If `HEAD.number == 0`:
HEAD.udReevalTime = udReevalTime0
Else if `HEAD~1.udReevalTime <= HEAD.medianTime`:
HEAD.udReevalTime = HEAD~1.udReevalTime + dtReeval
Else:
HEAD.udReevalTime = HEAD~1.udReevalTime
EndIf
###### BR_G12 - HEAD.unitBase
If `HEAD.number == 0`:
HEAD.unitBase = 0
Else:
HEAD.unitBase = HEAD~1.unitBase
###### BR_G13 - HEAD.dividend and HEAD.new_dividend
If `HEAD.number == 0`:
HEAD.dividend = ud0
Else If `HEAD.udReevalTime != HEAD~1.udReevalTime`:
HEAD.dividend = CEIL(HEAD_1.dividend + c² * CEIL(HEAD~1.massReeval / POW(10, HEAD~1.unitbase)) / HEAD.membersCount)
Else:
HEAD.dividend = HEAD~1.dividend
EndIf
If `HEAD.number == 0`:
HEAD.new_dividend = null
Else If `HEAD.udTime != HEAD~1.udTime`:
HEAD.new_dividend = HEAD.dividend
Else:
HEAD.new_dividend = null
###### BR_G14 - HEAD.dividend and HEAD.unitbase and HEAD.new_dividend
If `HEAD.dividend >= 1000000` :
HEAD.dividend = CEIL(HEAD.dividend / 10)
HEAD.new_dividend = HEAD.dividend
HEAD.unitBase = HEAD.unitBase + 1
###### BR_G15 - HEAD.mass and HEAD.massReeval
If `HEAD.number == 0`:
HEAD.mass = 0
Else if `HEAD.udTime != HEAD~1.udTime`:
HEAD.mass = HEAD~1.mass + HEAD.dividend * POWER(10, HEAD.unitBase) * HEAD.membersCount
Else:
HEAD.mass = HEAD~1.mass
EndIf
If `HEAD.number == 0`:
HEAD.massReeval = 0
Else if `HEAD.udReevalTime != HEAD~1.udReevalTime`:
HEAD.massReeval = HEAD~1.mass
Else:
HEAD.massReeval = HEAD~1.massReeval
EndIf
> Functionnally: the UD is reevaluated on the preceding monetary mass (important!)
###### BR_G16 - HEAD.speed
If `HEAD.number == 0`:
speed = 0
Else:
range = MIN(dtDiffEval, HEAD.number)
elapsed = (HEAD.medianTime - HEAD~<range>.medianTime)
EndIf
If `elapsed == 0`:
speed = 100
Else:
speed = range / elapsed
###### BR_G17 - HEAD.powMin
If `HEAD.number > 0 AND HEAD.diffNumber != HEAD~1.diffNumber AND HEAD.speed >= maxSpeed AND (HEAD~1.powMin + 2) % 16 == 0`:
HEAD.powMin = HEAD~1.powMin + 2
Else if `HEAD.number > 0 AND HEAD.diffNumber != HEAD~1.diffNumber AND HEAD.speed >= maxSpeed`:
HEAD.powMin = HEAD~1.powMin + 1
Else if `HEAD.number > 0 AND HEAD.diffNumber != HEAD~1.diffNumber AND HEAD.speed <= minSpeed AND (HEAD~1.powMin) % 16 == 0`:
HEAD.powMin = MAX(0, HEAD~1.powMin - 2)
Else if `HEAD.number > 0 AND HEAD.diffNumber != HEAD~1.diffNumber AND HEAD.speed <= minSpeed`:
HEAD.powMin = MAX(0, HEAD~1.powMin - 1)
Else if `HEAD.number > 0`:
HEAD.powMin = HEAD~1.powMin
###### BR_G18 - HEAD.powZeros and HEAD.powRemainder
If `HEAD.number == 0`:
nbPersonalBlocksInFrame = 0
medianOfBlocksInFrame = 1
Else:
blocksOfIssuer = HEAD~1..<HEAD~1.issuersFrame>[issuer=HEAD.issuer]
nbPersonalBlocksInFrame = COUNT(blocksOfIssuer)
blocksPerIssuerInFrame = MAP(
UNIQ((HEAD~1..<HEAD~1.issuersFrame>).issuer)
=> ISSUER: COUNT(HEAD~1..<HEAD~1.issuersFrame>[issuer=ISSUER]))
medianOfBlocksInFrame = MEDIAN(blocksPerIssuerInFrame)
EndIf
If `nbPersonalBlocksInFrame == 0`:
nbPreviousIssuers = 0
nbBlocksSince = 0
Else:
last = FIRST(blocksOfIssuer)
nbPreviousIssuers = last.issuersCount
nbBlocksSince = HEAD~1.number - last.number
EndIf
PERSONAL_EXCESS = MAX(0, ( (nbPersonalBlocksInFrame + 1) / medianOfBlocksInFrame) - 1)
PERSONAL_HANDICAP = FLOOR(LN(1 + PERSONAL_EXCESS) / LN(1.189))
HEAD.issuerDiff = MAX [ HEAD.powMin ; HEAD.powMin * FLOOR (percentRot * nbPreviousIssuers / (1 + nbBlocksSince)) ] + PERSONAL_HANDICAP
If `(HEAD.issuerDiff + 1) % 16 == 0`:
HEAD.issuerDiff = HEAD.issuerDiff + 1
EndIf
Finally:
HEAD.powRemainder = HEAD.issuerDiff % 16
HEAD.powZeros = (HEAD.issuerDiff - HEAD.powRemainder) / 16
###### Local IINDEX augmentation
####### BR_G19 - ENTRY.age
For each ENTRY in local IINDEX where `op = 'CREATE'`:
REF_BLOCK = HEAD~<HEAD~1.number + 1 - NUMBER(ENTRY.created_on)>[hash=HASH(ENTRY.created_on)]
If `HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'`:
ENTRY.age = 0
Else if `REF_BLOC != null`:
ENTRY.age = HEAD~1.medianTime - REF_BLOCK.medianTime
Else:
ENTRY.age = conf.idtyWindow + 1
EndIf
###### BR_G20 - Identity UserID unicity
For each ENTRY in local IINDEX:
If `op = 'CREATE'`:
ENTRY.uidUnique = COUNT(GLOBAL_IINDEX[uid=ENTRY.uid) == 0
Else:
ENTRY.uidUnique = true
###### BR_G21 - Identity pubkey unicity
For each ENTRY in local IINDEX:
If `op = 'CREATE'`:
ENTRY.pubUnique = COUNT(GLOBAL_IINDEX[pub=ENTRY.pub) == 0
Else:
ENTRY.pubUnique = true
####### BR_G33 - ENTRY.excludedIsMember
For each ENTRY in local IINDEX where `member != false`:
ENTRY.excludedIsMember = true
For each ENTRY in local IINDEX where `member == false`:
ENTRY.excludedIsMember = REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]).member
####### BR_G35 - ENTRY.isBeingKicked
For each ENTRY in local IINDEX where `member != false`:
ENTRY.isBeingKicked = false
For each ENTRY in local IINDEX where `member == false`:
ENTRY.isBeingKicked = true
####### BR_G36 - ENTRY.hasToBeExcluded
For each ENTRY in local IINDEX:
ENTRY.hasToBeExcluded = REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]).kick
###### Local MINDEX augmentation
####### BR_G22 - ENTRY.age
For each ENTRY in local MINDEX where `revoked_on == null`:
REF_BLOCK = HEAD~<HEAD~1.number + 1 - NUMBER(ENTRY.created_on)>[hash=HASH(ENTRY.created_on)]
If `HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'`:
ENTRY.age = 0
Else if `REF_BLOC != null`:
ENTRY.age = HEAD~1.medianTime - REF_BLOCK.medianTime
Else:
ENTRY.age = conf.msWindow + 1
EndIf
####### BR_G23 - ENTRY.numberFollowing
For each ENTRY in local MINDEX where `revoked_on == null`:
created_on = REDUCE(GLOBAL_MINDEX[pub=ENTRY.pub]).created_on
If `created_on != null`:
ENTRY.numberFollowing = NUMBER(ENTRY.created_ON) > NUMBER(created_on)
Else:
ENTRY.numberFollowing = true
EndIf
For each ENTRY in local MINDEX where `revoked_on != null`:
ENTRY.numberFollowing = true
####### BR_G24 - ENTRY.distanceOK
For each ENTRY in local MINDEX where `type == 'JOIN' OR type == 'ACTIVE'`:
dSen = CEIL(HEAD.membersCount ^ (1 / stepMax))
GRAPH = SET(LOCAL_CINDEX, 'issuer', 'receiver') + SET(GLOBAL_CINDEX, 'issuer', 'receiver')
SENTRIES = SUBSET(GRAPH, dSen, 'issuer')
ENTRY.distanceOK = EXISTS_PATH(xpercent, SENTRIES, GRAPH, ENTRY.pub, stepMax)
> Functionally: checks if it exists, for at least `xpercent`% of the sentries, a path using GLOBAL_CINDEX + LOCAL_CINDEX leading to the key `PUBLIC_KEY` with a maximum count of `[stepMax]` hops.
For each ENTRY in local MINDEX where `!(type == 'JOIN' OR type == 'ACTIVE')`:
ENTRY.distanceOK = true
####### BR_G25 - ENTRY.onRevoked
For each ENTRY in local MINDEX:
ENTRY.onRevoked = REDUCE(GLOBAL_MINDEX[pub=ENTRY.pub]).revoked_on != null
####### BR_G26 - ENTRY.joinsTwice
For each ENTRY in local MINDEX where `op = 'UPDATE', expired_on = 0`:
ENTRY.joinsTwice = REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]).member == true
> This rule ensures that someone who is in the `Joiners` field isn't already a member.
####### BR_G27 - ENTRY.enoughCerts
For each ENTRY in local MINDEX where `type == 'JOIN' OR type == 'ACTIVE'`:
ENTRY.enoughCerts = COUNT(GLOBAL_CINDEX[receiver=ENTRY.pub,expired_on=null]) + COUNT(LOCAL_CINDEX[receiver=ENTRY.pub,expired_on=null]) >= sigQty
> Functionally: any member or newcomer needs `[sigQty]` certifications coming *to* him to be in the WoT
For each ENTRY in local MINDEX where `!(type == 'JOIN' OR type == 'ACTIVE')`:
ENTRY.enoughCerts = true
####### BR_G28 - ENTRY.leaverIsMember
For each ENTRY in local MINDEX where `type == 'LEAVE'`:
ENTRY.leaverIsMember = REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]).member
For each ENTRY in local MINDEX where `type != 'LEAVE'`:
ENTRY.leaverIsMember = true
####### BR_G29 - ENTRY.activeIsMember
For each ENTRY in local MINDEX where `type == 'ACTIVE'`:
ENTRY.activeIsMember = REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]).member
For each ENTRY in local MINDEX where `type != 'ACTIVE'`:
ENTRY.activeIsMember = true
####### BR_G30 - ENTRY.revokedIsMember
For each ENTRY in local MINDEX where `revoked_on == null`:
ENTRY.revokedIsMember = true
For each ENTRY in local MINDEX where `revoked_on != null`:
ENTRY.revokedIsMember = REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]).member
####### BR_G31 - ENTRY.alreadyRevoked
For each ENTRY in local MINDEX where `revoked_on == null`:
ENTRY.alreadyRevoked = false
For each ENTRY in local MINDEX where `revoked_on != null`:
ENTRY.alreadyRevoked = REDUCE(GLOBAL_MINDEX[pub=ENTRY.pub]).revoked_on != null
####### BR_G32 - ENTRY.revocationSigOK
For each ENTRY in local MINDEX where `revoked_on == null`:
ENTRY.revocationSigOK = true
For each ENTRY in local MINDEX where `revoked_on != null`:
ENTRY.revocationSigOK = SIG_CHECK_REVOKE(REDUCE(GLOBAL_IINDEX[pub=ENTRY.pub]), ENTRY)
####### BR_G34 - ENTRY.isBeingRevoked
For each ENTRY in local MINDEX where `revoked_on == null`:
ENTRY.isBeingRevoked = false
For each ENTRY in local MINDEX where `revoked_on != null`:
ENTRY.isBeingRevoked = true
####### BR_G107 - ENTRY.unchainables
If `HEAD.number > 0 AND ENTRY.revocation == null`:
ENTRY.unchainables = COUNT(GLOBAL_MINDEX[issuer=ENTRY.issuer, chainable_on > HEAD~1.medianTime]))
###### Local CINDEX augmentation
For each ENTRY in local CINDEX:
####### BR_G37 - ENTRY.age
REF_BLOCK = HEAD~<HEAD~1.number + 1 - NUMBER(ENTRY.created_on)>[hash=HASH(ENTRY.created_on)]
If `HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'`:
ENTRY.age = 0
Else if `REF_BLOC != null`:
ENTRY.age = HEAD~1.medianTime - REF_BLOCK.medianTime
Else:
ENTRY.age = conf.sigWindow + 1
EndIf
####### BR_G38 - ENTRY.unchainables
If `HEAD.number > 0`:
ENTRY.unchainables = COUNT(GLOBAL_CINDEX[issuer=ENTRY.issuer, chainable_on > HEAD~1.medianTime]))
####### BR_G39 - ENTRY.stock
ENTRY.stock = COUNT(REDUCE_BY(GLOBAL_CINDEX[issuer=ENTRY.issuer], 'receiver', 'created_on')[expired_on=0])
####### BR_G40 - ENTRY.fromMember
ENTRY.fromMember = REDUCE(GLOBAL_IINDEX[pub=ENTRY.issuer]).member
####### BR_G41 - ENTRY.toMember
ENTRY.toMember = REDUCE(GLOBAL_IINDEX[pub=ENTRY.receiver]).member
####### BR_G42 - ENTRY.toNewcomer
ENTRY.toNewcomer = COUNT(LOCAL_IINDEX[member=true,pub=ENTRY.receiver]) > 0
####### BR_G43 - ENTRY.toLeaver
ENTRY.toLeaver = REDUCE(GLOBAL_MINDEX[pub=ENTRY.receiver]).leaving
####### BR_G44 - ENTRY.isReplay
reducable = GLOBAL_CINDEX[issuer=ENTRY.issuer,receiver=ENTRY.receiver,expired_on=0]
If `count(reducable) == 0`:
ENTRY.isReplay = false
Else:
ENTRY.isReplay = reduce(reducable).expired_on == 0
####### BR_G44.2 - ENTRY.isReplayable
If `HEAD.number > 0 && HEAD~1.version > 10` :
reducable = GLOBAL_CINDEX[issuer=ENTRY.issuer,receiver=ENTRY.receiver,expired_on=0]
If `count(reducable) == 0`:
ENTRY.isReplayable = true
Else:
ENTRY.isReplayable = reduce(reducable).replayable_on < HEAD~1.medianTime
Else:
ENTRY.isReplayable = false
EndIf
####### BR_G45 - ENTRY.sigOK
ENTRY.sigOK = SIG_CHECK_CERT(REDUCE(GLOBAL_IINDEX[pub=ENTRY.receiver]), ENTRY)
###### Local SINDEX augmentation
####### BR_G102 - ENTRY.age
For each ENTRY in local SINDEX where `op = 'UPDATE'`:
REF_BLOCK = HEAD~<HEAD~1.number + 1 - NUMBER(ENTRY.hash)>[hash=HASH(ENTRY.created_on)]
If `HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'`:
ENTRY.age = 0
Else if `REF_BLOC != null`:
ENTRY.age = HEAD~1.medianTime - REF_BLOCK.medianTime
Else:
ENTRY.age = conf.txWindow + 1
EndIf
####### BR_G46 - ENTRY.available and ENTRY.conditions
For each `LOCAL_SINDEX[op='UPDATE'] as ENTRY`:
INPUT_ENTRIES = LOCAL_SINDEX[op='CREATE',identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]
If COUNT(INPUT_ENTRIES) == 0 Then
INPUT_ENTRIES = GLOBAL_SINDEX[identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]
EndIf
INPUT = REDUCE(INPUT_ENTRIES)
ENTRY.conditions = INPUT.conditions
ENTRY.available = INPUT.consumed == false
####### BR_G47 - ENTRY.isLocked
INPUT_ENTRIES = LOCAL_SINDEX[op='CREATE',identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]
If COUNT(INPUT_ENTRIES) == 0 Then
INPUT_ENTRIES = GLOBAL_SINDEX[identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]
EndIf
INPUT = REDUCE(INPUT_ENTRIES)
ENTRY.isLocked = TX_SOURCE_UNLOCK(INPUT.conditions, ENTRY)
####### BR_G48 - ENTRY.isTimeLocked
INPUT_ENTRIES = LOCAL_SINDEX[op='CREATE',identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]
If COUNT(INPUT_ENTRIES) == 0 Then
INPUT_ENTRIES = GLOBAL_SINDEX[identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]
EndIf
INPUT = REDUCE(INPUT_ENTRIES)
ENTRY.isTimeLocked = ENTRY.written_time - INPUT.written_time < ENTRY.locktime
##### Rules
Each rule returns true by default, unless **at least one test returns `false`**.
###### BR_G49 - Version
Rule:
If `HEAD.number > 0`:
HEAD.version == (HEAD~1.version OR HEAD~1.version + 1)
###### BR_G50 - Block size
Rule:
If `HEAD.number > 0`:
HEAD.size < MAX(500 ; CEIL(1.10 * HEAD.avgBlockSize))
###### BR_G98 - Currency
Rule:
If `HEAD.number > 0`:
Currency = HEAD.currency
###### BR_G51 - Number
Rule:
Number = HEAD.number
###### BR_G52 - PreviousHash
Rule:
PreviousHash = HEAD.previousHash
###### BR_G53 - PreviousIssuer
Rule:
PreviousHash = HEAD.previousHash
###### BR_G101 - Issuer
Rule:
HEAD.issuerIsMember == true
###### BR_G54 - DifferentIssuersCount
Rule:
DifferentIssuersCount = HEAD.issuersCount
###### BR_G55 - IssuersFrame
Rule:
IssuersFrame = HEAD.issuersFrame
###### BR_G56 - IssuersFrameVar
Rule:
IssuersFrameVar = HEAD.issuersFrameVar
###### BR_G57 - MedianTime
Rule:
MedianTime = HEAD.medianTime
###### BR_G58 - UniversalDividend
Rule:
UniversalDividend = HEAD.new_dividend
###### BR_G59 - UnitBase
Rule:
UnitBase = HEAD.unitBase
###### BR_G60 - MembersCount
Rule:
MembersCount = HEAD.membersCount
###### BR_G61 - PowMin
Rule:
If `HEAD.number > 0`:
PowMin = HEAD.powMin
###### BR_G62 - Proof-of-work
Rule: the proof is considered valid if:
* `HEAD.hash` starts with at least `HEAD.powZeros` zeros
* `HEAD.hash`'s `HEAD.powZeros + 1`th character is:
* between `[0-F]` if `HEAD.powRemainder = 0`
* between `[0-E]` if `HEAD.powRemainder = 1`
* between `[0-D]` if `HEAD.powRemainder = 2`
* between `[0-C]` if `HEAD.powRemainder = 3`
* between `[0-B]` if `HEAD.powRemainder = 4`
* between `[0-A]` if `HEAD.powRemainder = 5`
* between `[0-9]` if `HEAD.powRemainder = 6`
* between `[0-8]` if `HEAD.powRemainder = 7`
* between `[0-7]` if `HEAD.powRemainder = 8`
* between `[0-6]` if `HEAD.powRemainder = 9`
* between `[0-5]` if `HEAD.powRemainder = 10`
* between `[0-4]` if `HEAD.powRemainder = 11`
* between `[0-3]` if `HEAD.powRemainder = 12`
* between `[0-2]` if `HEAD.powRemainder = 13`
* between `[0-1]` if `HEAD.powRemainder = 14`
> N.B.: it is not possible to have HEAD.powRemainder = 15
###### BR_G63 - Identity writability
Rule:
ENTRY.age <= [idtyWindow]
###### BR_G64 - Membership writability
Rule:
ENTRY.age <= [msWindow]
###### BR_G108 - Membership period
Rule:
ENTRY.unchainables == 0
###### BR_G65 - Certification writability
Rule:
ENTRY.age <= [sigWindow]
###### BR_G66 - Certification stock
Rule:
ENTRY.stock <= sigStock
###### BR_G67 - Certification period
Rule:
ENTRY.unchainables == 0
###### BR_G68 - Certification from member
Rule:
If `HEAD.number > 0`:
ENTRY.fromMember == true
###### BR_G69 - Certification to member or newcomer
Rule:
ENTRY.toMember == true
OR
ENTRY.toNewcomer == true
###### BR_G70 - Certification to non-leaver
Rule:
ENTRY.toLeaver == false
###### BR_G71 - Certification replay
Rule:
ENTRY.isReplay == false || ENTRY.isReplayable == true
###### BR_G72 - Certification signature
Rule:
ENTRY.sigOK == true
###### BR_G73 - Identity UserID unicity
Rule:
ENTRY.uidUnique == true
###### BR_G74 - Identity pubkey unicity
Rule:
ENTRY.pubUnique == true
###### BR_G75 - Membership succession
Rule:
ENTRY.numberFollowing == true
###### BR_G76 - Membership distance check
Rule:
ENTRY.distanceOK == true
###### BR_G77 - Membership on revoked
Rule:
ENTRY.onRevoked == false
###### BR_G78 - Membership joins twice
Rule:
ENTRY.joinsTwice == false
###### BR_G79 - Membership enough certifications
Rule:
ENTRY.enoughCerts == true
###### BR_G80 - Membership leaver
Rule:
ENTRY.leaverIsMember == true
###### BR_G81 - Membership active
Rule:
ENTRY.activeIsMember == true
###### BR_G82 - Revocation by a member
Rule:
ENTRY.revokedIsMember == true
###### BR_G83 - Revocation singleton
Rule:
ENTRY.alreadyRevoked == false
###### BR_G84 - Revocation signature
Rule:
ENTRY.revocationSigOK == true
###### BR_G85 - Excluded is a member
Rule:
ENTRY.excludedIsMember == true
###### BR_G86 - Excluded contains exclatly those to be kicked
Rule:
For each `REDUCE_BY(GLOBAL_IINDEX[kick=true], 'pub') as TO_KICK`:
REDUCED = REDUCE(GLOBAL_IINDEX[pub=TO_KICK.pub])
If `REDUCED.kick` then:
COUNT(LOCAL_MINDEX[pub=REDUCED.pub,isBeingKicked=true]) == 1
Rule:
For each `IINDEX[member=false] as ENTRY`:
ENTRY.hasToBeExcluded = true
###### BR_G103 - Trancation writability
Rule:
ENTRY.age <= [txWindow]
###### BR_G87 - Input is available
For each `LOCAL_SINDEX[op='UPDATE'] as ENTRY`:
Rule:
ENTRY.available == true
###### BR_G88 - Input is unlocked
For each `LOCAL_SINDEX[op='UPDATE'] as ENTRY`:
Rule:
ENTRY.isLocked == false
###### BR_G89 - Input is time unlocked
For each `LOCAL_SINDEX[op='UPDATE'] as ENTRY`:
Rule:
ENTRY.isTimeLocked == false
###### BR_G90 - Output base
For each `LOCAL_SINDEX[op='CREATE'] as ENTRY`:
Rule:
ENTRY.unitBase <= HEAD~1.unitBase
##### Post-rules INDEX augmentation
###### BR_G91 - Dividend
If `HEAD.new_dividend != null`:
For each `REDUCE_BY(GLOBAL_IINDEX[member=true], 'pub') as IDTY` then if `IDTY.member`, add a new LOCAL_SINDEX entry:
SINDEX (
op = 'CREATE'
identifier = IDTY.pub
pos = HEAD.number
written_on = BLOCKSTAMP
written_time = MedianTime
amount = HEAD.dividend
base = HEAD.unitBase
locktime = null
conditions = REQUIRE_SIG(MEMBER.pub)
consumed = false
)
For each `LOCAL_IINDEX[member=true] as IDTY` add a new LOCAL_SINDEX entry:
SINDEX (
op = 'CREATE'
identifier = IDTY.pub
pos = HEAD.number
written_on = BLOCKSTAMP
written_time = MedianTime
amount = HEAD.dividend
base = HEAD.unitBase
locktime = null
conditions = REQUIRE_SIG(MEMBER.pub)
consumed = false
)
###### BR_G106 - Low accounts
Set:
ACCOUNTS = UNIQ(GLOBAL_SINDEX, 'conditions')
For each `ACCOUNTS as ACCOUNT` then:
Set:
ALL_SOURCES = CONCAT(GLOBAL_SINDEX[conditions=ACCOUNT.conditions], LOCAL_SINDEX[conditions=ACCOUNT.conditions])
SOURCES = REDUCE_BY(ALL_SOURCES, 'identifier', 'pos')[consumed=false]
BALANCE = SUM(MAP(SOURCES => SRC: SRC.amount * POW(10, SRC.base)))
If `BALANCE < 100 * POW(10, HEAD.unitBase)`, then for each `SOURCES AS SRC` add a new LOCAL_SINDEX entry:
SINDEX (
op = 'UPDATE'
identifier = SRC.identifier
pos = SRC.pos
written_on = BLOCKSTAMP
written_time = MedianTime
consumed = true
)
###### BR_G92 - Certification expiry
For each `GLOBAL_CINDEX[expires_on<=HEAD.medianTime] as CERT`, add a new LOCAL_CINDEX entry:
If `reduce(GLOBAL_CINDEX[issuer=CERT.issuer,receiver=CERT.receiver,created_on=CERT.created_on]).expired_on == 0`:
CINDEX (
op = 'UPDATE'
issuer = CERT.issuer
receiver = CERT.receiver
created_on = CERT.created_on
expired_on = HEAD.medianTime
)
###### BR_G93 - Membership expiry
For each `REDUCE_BY(GLOBAL_MINDEX[expires_on<=HEAD.medianTime AND revokes_on>HEAD.medianTime], 'pub') as POTENTIAL` then consider `REDUCE(GLOBAL_MINDEX[pub=POTENTIAL.pub]) AS MS`.
If `(MS.expired_on == null OR MS.expired_on == 0) AND MS.expires_on > HEAD.medianTime`, add a new LOCAL_MINDEX entry:
MINDEX (
op = 'UPDATE'
pub = MS.pub
written_on = BLOCKSTAMP
expired_on = HEAD.medianTime
)
###### BR_G94 - Exclusion by membership
For each `LOCAL_MINDEX[expired_on!=0] as MS`, add a new LOCAL_IINDEX entry:
IINDEX (
op = 'UPDATE'
pub = MS.pub
written_on = BLOCKSTAMP
kick = true
)
###### BR_G95 - Exclusion by certification
For each `LOCAL_CINDEX[expired_on!=0] as CERT`:
Set:
CURRENT_VALID_CERTS = REDUCE_BY(GLOBAL_CINDEX[receiver=CERT.receiver], 'issuer', 'receiver', 'created_on')[expired_on=0]
If `COUNT(CURRENT_VALID_CERTS) + COUNT(LOCAL_CINDEX[receiver=CERT.receiver,expired_on=0]) - COUNT(LOCAL_CINDEX[receiver=CERT.receiver,expired_on!=0]) < sigQty`, add a new LOCAL_IINDEX entry:
IINDEX (
op = 'UPDATE'
pub = CERT.receiver
written_on = BLOCKSTAMP
kick = true
)
###### BR_G96 - Implicit revocation
For each `GLOBAL_MINDEX[revokes_on<=HEAD.medianTime,revoked_on=null] as MS`:
REDUCED = REDUCE(GLOBAL_MINDEX[pub=MS.pub])
If `REDUCED.revokes_on<=HEAD.medianTime AND REDUCED.revoked_on==null`, add a new LOCAL_MINDEX entry:
MINDEX (
op = 'UPDATE'
pub = MS.pub
written_on = BLOCKSTAMP
revoked_on = HEAD.medianTime
)
###### BR_G104 - Membership expiry date correction
For each `LOCAL_MINDEX[type='JOIN'] as MS`:
MS.expires_on = MS.expires_on - MS.age
MS.revokes_on = MS.revokes_on - MS.age
For each `LOCAL_MINDEX[type='ACTIVE'] as MS`:
MS.expires_on = MS.expires_on - MS.age
MS.revokes_on = MS.revokes_on - MS.age
###### BR_G105 - Certification expiry date correction
For each `LOCAL_CINDEX as CERT`:
CERT.expires_on = CERT.expires_on - CERT.age
##### BR_G97 - Final INDEX operations
If all the rules [BR_G49 ; BR_G90] pass, then all the LOCAL INDEX values (IINDEX, MINDEX, CINDEX, SINDEX, BINDEX) have to be appended to the GLOBAL INDEX.
### Peer
#### Global validation
##### Block
* `Block` field must target an existing block in the blockchain, or target special block `0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855`.
#### Interpretation
* A Peer document SHOULD NOT be interpreted if its `Block` field is anterior to previously recorded Peer document for a same `PublicKey` key.
### Transactions
#### Local coherence
* A transaction must be signed by all of its `Issuers`
* Signatures are to be associated to each `Issuers` according to their apparition order
* A transaction must have at least 1 issuer
* A transaction must have at least 1 source
* A transaction must have at least 1 recipient
* A transaction must have at least 1 signature
* A transaction must have exactly the same number of signatures and issuers
* A transaction's indexes' (`Inputs` field) value must be less than or equal to the `Issuers` line count
* A transaction must have its issuers appear at least once in the `Inputs` field, where an issuer is linked to `INDEX` by its position in the `Issuers` field. First issuer is `INDEX = 0`.
* A transaction's `Inputs` amount sum must be equal to the `Ouputs` amount sum.
* A transaction cannot have two identical `Inputs`
## Implementations
### APIs
UCP does not impose any particular APIs to deal with UCP data. Instead, UCP prefers to allow for any API definitions using [Peer](#peer) document, and then letting peers deal with the API(s) they prefer themselves.
At this stage, only the [Duniter HTTP API](/HTTP_API.md) (named BASIC_MERKLED_API) is known as a valid UCP API.
## References
* [Relative Theory of the Money](http://en.trm.creationmonetaire.info), the theoretical reference behind Universal Dividend
* [OpenUDC](http://www.openudc.org/), the inspiration project of Duniter
* [Bitcoin](https://github.com/bitcoin/bitcoin), the well known crypto-currency system
See [RFC 0004: Duniter Network Protocol V1](https://git.duniter.org/nodes/common/doc/blob/master/rfc/0004_ws2p_v1.md).
\ No newline at end of file
Loading