Protocol.md
- DUP - Duniter Protocol
- Contents
- Vocabulary
- Introduction
- Conventions
- Documents
- Line endings
- Numbering
- Block identifiers
- BLOCK_ID
- BLOCK_UID
- Examples
- Currency name
- Datesœ
- Integer
- Signatures
- Format
- Line endings
- Formats
- Public key
- Definition
- Identity
- Definition
- Identity unique ID
- Format
- Example
- Revocation
- Definition
- Use case
- Format
- Example
- Certification
- Definition
- Format
- Inline format
- Example
- Membership
- Fields details
- Validity
- Transaction
- Definition
- Money ownership
- Transfering money
- Format
- Validity
- Input condition
- Input condition examples
- Output condition
- Output condition examples
- Condition matching
- Example 1
- Example 2
- Unlocking functions
- SIG function
- XHX function
- Example 1
- Example 2
- Example 3
- CLTV function
- CSV function
- Compact format
- Block
- Structure
- BlockSize
- Coherence
- Format
- Data
- Peer
- Structure
- Fields details
- Coherence
- Format
- Example
- Variables
- Protocol parameters
- Computed variables
- Processing
- Block
- Local validation
- Version
- InnerHash
- Nonce
- Proof of work
- PreviousHash
- PreviousIssuer
- Parameters
- Universal Dividend
- UnitBase
- Signature
- Dates
- Identities
- Memberships (Joiners, Actives, Leavers)
- Revoked
- Transactions
- INDEX GENERATION
- Identities
- Joiners
- Actives
- Leavers
- Revoked
- Excluded
- Certifications
- Sources
- INDEX RULES
- UserID and PublicKey unicity
- Membership unicity
- Revocation implies exclusion
- Certifications
- Sources
- Double-spending control
- CommonBase
- Amounts
- Global
- INDEX GENERATION
- Definitions
- HEAD
- BR_G01 - HEAD.number
- BR_G02 - HEAD.previousHash
- BR_G99 - HEAD.currency
- BR_G03 - HEAD.previousIssuer
- BR_G100 - HEAD.issuerIsMember
- BR_G04 - HEAD.issuersCount
- BR_G05 - HEAD.issuersFrame
- BR_G06 - HEAD.issuersFrameVar
- BR_G07 - HEAD.avgBlockSize
- BR_G08 - HEAD.medianTime
- BR_G09 - HEAD.diffNumber
- BR_G10 - HEAD.membersCount
- BR_G11 - HEAD.udTime and HEAD.udReevalTime
- BR_G12 - HEAD.unitBase
- BR_G13 - HEAD.dividend and HEAD.new_dividend
- BR_G14 - HEAD.dividend and HEAD.unitbase and HEAD.new_dividend
- BR_G15 - HEAD.mass and HEAD.massReeval
- BR_G16 - HEAD.speed
- BR_G17 - HEAD.powMin
- BR_G18 - HEAD.powZeros and HEAD.powRemainder
- Local IINDEX augmentation
- BR_G20 - Identity UserID unicity
- BR_G21 - Identity pubkey unicity
- Local MINDEX augmentation
- Local CINDEX augmentation
- Local SINDEX augmentation
- Rules
- BR_G49 - Version
- BR_G50 - Block size
- BR_G98 - Currency
- BR_G51 - Number
- BR_G52 - PreviousHash
- BR_G53 - PreviousIssuer
- BR_G101 - Issuer
- BR_G54 - DifferentIssuersCount
- BR_G55 - IssuersFrame
- BR_G56 - IssuersFrameVar
- BR_G57 - MedianTime
- BR_G58 - UniversalDividend
- BR_G59 - UnitBase
- BR_G60 - MembersCount
- BR_G61 - PowMin
- BR_G62 - Proof-of-work
- BR_G63 - Identity writability
- BR_G64 - Membership writability
- BR_G108 - Membership period
- BR_G65 - Certification writability
- BR_G66 - Certification stock
- BR_G67 - Certification period
- BR_G68 - Certification from member
- BR_G69 - Certification to member or newcomer
- BR_G70 - Certification to non-leaver
- BR_G71 - Certification replay
- BR_G72 - Certification signature
- BR_G73 - Identity UserID unicity
- BR_G74 - Identity pubkey unicity
- BR_G75 - Membership succession
- BR_G76 - Membership distance check
- BR_G77 - Membership on revoked
- BR_G78 - Membership joins twice
- BR_G79 - Membership enough certifications
- BR_G80 - Membership leaver
- BR_G81 - Membership active
- BR_G82 - Revocation by a member
- BR_G83 - Revocation singleton
- BR_G84 - Revocation signature
- BR_G85 - Excluded is a member
- BR_G86 - Excluded contains exclatly those to be kicked
- BR_G103 - Trancation writability
- BR_G87 - Input is available
- BR_G88 - Input is unlocked
- BR_G89 - Input is time unlocked
- BR_G90 - Output base
- Post-rules INDEX augmentation
- BR_G91 - Dividend
- BR_G106 - Low accounts
- BR_G92 - Certification expiry
- BR_G93 - Membership expiry
- BR_G94 - Exclusion by membership
- BR_G95 - Exclusion by certification
- BR_G96 - Implicit revocation
- BR_G104 - Membership expiry date correction
- BR_G105 - Certification expiry date correction
- BR_G97 - Final INDEX operations
- Peer
- Global validation
- Block
- Interpretation
- Transactions
- Local coherence
- Implementations
- APIs
- References
DUP - Duniter Protocol
This document reflects Duniter in-production protocol. It is updated only for clarifications (2017).
Contents
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 numbering starts from 0
. That is, first block is BLOCK#0
.
Block identifiers
There are 2 kinds of 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 Ed55219 pattern, and are written under 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 Ed55219 public key.
Its format is a 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:
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:
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:
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:
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:
Version: 10
Type: Identity
Currency: beta_brousouf
Issuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
UniqueID: lolcat
Timestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
A valid revocation could be:
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:
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:
Version: 10
Type: Identity
Currency: beta_brousouf
Issuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
UniqueID: lolcat
Timestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
A valid certification could be:
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:
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 is to be considered having valid format if:
-
Version
equals2
-
Type
equalsMembership
value. -
Currency
is a valid currency name -
Issuer
is a public key -
Membership
matches eitherIN
orOUT
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:
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
equals2
or3
. - Field
Type
equalsTransaction
. - 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 followINDEX:UL_CONDITIONS
format:-
IN_INDEX
must be an integer value -
UL_CONDITIONS
must be a valid Input Condition
-
- Field
Outputs
is a multiline field whose lines followAMOUNT:BASE:CONDITIONS
format:-
AMOUNT
must be an integer value -
BASE
must be an integer value -
CONDITIONS
must be a valid 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 -
An empty condition or a condition fully composed of spaces is considered an invalid output condition.
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
Each Unlock
of TX2 refers to an input of TX2 through IN_INDEX
, input itself refering to an Output
of TX1 through T_HASH
reference and T_INDEX
.
- An output contains
F
functions in its conditions (read from left to right) - An unlock contains
P
parameters (or less, min. is zero), each separated by a space (read from left to right)
A function of TX1 at position f
returns TRUE if parameter at position p
resolves the function. Otherwise it returns FALSE.
The condition of an Output
is unlocked if, evaluated globally with (
, )
, &&
, and ||
, the condition returns TRUE.
Example 1
TX1:
Outputs:
50:2:XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)
Is resolved by TX2:
Unlocks:
0:XHX(1872767826647264)
Because XHX(1872767826647264) = 8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB
(this will be explained in the next section).
Example 2
TX1:
Outputs:
50:2:SIG(DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo)
Is resolved by TX2:
Issuers:
HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
[...]
Unlocks:
0:SIG(1)
Because SIG(1)
refers to the signature DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
, considering that signature DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
is good over TX2.
Unlocking functions
These functions may be present under both Unlocks
and Outputs
fields.
- When present under
Outputs
, these functions define the necessary conditions to spend each output. - When present under
Unlocks
, these functions define the sufficient proofs that each input can be spent.
SIG function
This function is a control over the signature.
- in an
Output
of TX1,SIG(PUBKEY_A)
requires from a future transaction TX2 unlocking the output to give as parameter a valid signature of TX2 byPUBKEY_A
- if TX2 does not give
SIG(INDEX)
parameter as matching parameter, the condition fails - if TX2's
Issuers[INDEX]
does not equalPUBKEY_A
, the condition fails - if TX2's
SIG(INDEX)
does not return TRUE, the condition fails
- if TX2 does not give
- in an
Unlock
of TX2,SIG(INDEX)
returns TRUE ifSignatures[INDEX]
is a valid signature of TX2 againstIssuers[INDEX]
So if we have, in TX1:
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:
Version: 10
Type: Transaction
[...]
Issuers:
BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g
Inputs:
25:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0
Unlocks:
0:SIG(0)
Where:
-
SIG(0)
refers to the signature ofIssuers[0]
, hereBYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g
-
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 againstIssuers[0]
public key Issuers[0] = BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g
XHX function
This function is a password control.
So if we have, in TX1:
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:
Version: 10
Type: Transaction
[...]
Issuers:
BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g
Inputs:
55:1:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0
Unlocks:
0:XHX(1872767826647264)
Where:
-
6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3
is the hash of TX1.
The necessary condition XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)
is matched here if XHX(1872767826647264) = 8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB
.
XHX(1872767826647264)
is to be evaluated as SHA256(1872767826647264)
.
Example 1
Key HsLShA
sending 30 coins to key BYfWYF
using 1 source transaction written in block #3 (closed).
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:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
5:2: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 (closed) and #77 (closed) + 1 UD from block #88 (closed).
Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
Locktime: 0
Issuers:
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
Inputs:
6:2: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 (closed).
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
This function locks an output in the future, which will be unlocked at a given date.
So if we have, in TX1:
Version: 10
Type: Transaction
[...]
Outputs
25:2:CLTV(1489677041)
Then 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
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:
Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
[...]
Outputs
25:2:CSV(3600)
We define TxTime
as the MedianTime
of block 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
.
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 document. The general format is:
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 from above:
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 from above:
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 data in order to build a Web Of Trust (WoT) representation
- Transaction data to identify money units & ownership
but also other informations like:
- time reference (calendar time)
- UD value for money issuance
Structure
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
andDifferentIssuersCount
are integer values -
Currency
is a valid currency name -
PreviousHash
is an uppercased SHA256 hash -
Issuer
andPreviousIssuer
are Public keys -
Identities
is a multiline field composed for each line of:-
PUBLIC_KEY
: a Public key -
SIGNATURE
: a Signature -
BLOCK_UID
: a block UID -
USER_ID
: an identifier
-
-
Joiners
,Actives
andLeavers
are multiline fields composed for each line of:-
PUBLIC_KEY
: a Public key -
SIGNATURE
: a 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 -
USER_ID
: an identifier
-
-
Excluded
is a multiline field composed for each line of:-
PUBLIC_KEY
: a Public key
-
-
Certifications
is a multiline field composed for each line of:-
PUBKEY_FROM
: a Public key doing the certification -
PUBKEY_TO
: a Public key being certified -
BLOCK_ID
: a positive integer -
SIGNATURE
: a Signature of the certification
-
-
Transactions
is a multiline field composed of compact transactions -
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 (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.
Data
-
Version
equals6
-
Type
equalsBlock
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
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
equals2
or3
-
Type
equalsPeer
-
Currency
is a valid currency name -
PublicKey
is a Public key -
Endpoints
is a multiline field
The document must be ended with a BOTTOM_SIGNATURE
Signature.
Example
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. |
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
InnerHash
-
InnerHash
is the SHA256 hash of the whole fields fromVersion:
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 theNumber
field is greater than0
. -
PreviousHash
must not be present if the value of theNumber
field is equal to0
.
PreviousIssuer
-
PreviousIssuer
must be present if the value of theNumber
field is greater than0
. -
PreviousIssuer
must not be present if the value of theNumber
field is equal to0
.
Parameters
-
Parameters
must be present if the value of theNumber
field is equal to0
. -
Parameters
must not be present if the value of theNumber
field is greater than0
.
Universal Dividend
If HEAD.number == 0, HEAD.dividend must equal null
.
UnitBase
- Block V2:
- If
UniversalDividend
field is present,UnitBase
must be present too.
- If
- Block V3:
- The field is always present.
- For root block,
UnitBase
must equal0
.
Signature
- A block must have a valid signature over the content from
Hash:
toNonce: NONCE\n
, where the associated public key is the block'sIssuer
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 withINDEX >=
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 = BLOCKSTAMP
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
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 MINDEXop = '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 = BLOCKSTAMP
operations must match a single local IINDEXop = '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
orPUBLIC_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 sameInputBase
. -
Def.:
OutputBaseSum
is the sum of amounts with the sameOutputBase
. -
Def.:
BaseDelta = OutputBaseSum - InputBaseSum
, expressed inCommonBase
-
Rule: For each
OutputBase
:- if
BaseDelta > 0
, then it must be inferior or equal to the sum of all precedingBaseDelta
- if
-
Rule: The sum of all inputs in
CommonBase
must equal the sum of all outputs inCommonBase
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.
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 1st entry before HEAD (= entry where
ENTRY.number = HEAD.number - 1
) -
HEAD~n: the BINDEX nth 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~..).property: same, but and 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
- 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 = 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 keyPUBLIC_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
####### 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_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 IINDEX 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 = REDUCE(GLOBAL_SINDEX[identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base])
ENTRY.conditions = INPUT.conditions
ENTRY.available = INPUT.consumed == false
####### BR_G47 - ENTRY.isLocked
ENTRY.isLocked = TX_SOURCE_UNLOCK(REDUCE(GLOBAL_SINDEX[identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]).conditions, ENTRY)
####### BR_G48 - ENTRY.isTimeLocked
ENTRY.isTimeLocked = ENTRY.written_time - REDUCE(GLOBAL_SINDEX[identifier=ENTRY.identifier,pos=ENTRY.pos,amount=ENTRY.amount,base=ENTRY.base]).written_time < ENTRY.locktime
Rules
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 leastHEAD.powZeros
zeros -
HEAD.hash
'sHEAD.powZeros + 1
th character is:- between
[0-F]
ifHEAD.powRemainder = 0
- between
[0-E]
ifHEAD.powRemainder = 1
- between
[0-D]
ifHEAD.powRemainder = 2
- between
[0-C]
ifHEAD.powRemainder = 3
- between
[0-B]
ifHEAD.powRemainder = 4
- between
[0-A]
ifHEAD.powRemainder = 5
- between
[0-9]
ifHEAD.powRemainder = 6
- between
[0-8]
ifHEAD.powRemainder = 7
- between
[0-7]
ifHEAD.powRemainder = 8
- between
[0-6]
ifHEAD.powRemainder = 9
- between
[0-5]
ifHEAD.powRemainder = 10
- between
[0-4]
ifHEAD.powRemainder = 11
- between
[0-3]
ifHEAD.powRemainder = 12
- between
[0-2]
ifHEAD.powRemainder = 13
- between
[0-1]
ifHEAD.powRemainder = 14
- between
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
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
)
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], 'pub') as POTENTIAL
then consider REDUCE(GLOBAL_MINDEX[pub=POTENTIAL.pub]) AS MS
.
If MS.expired_on == null OR MS.expired_on == 0
, 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 block0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
.
Interpretation
- A Peer document SHOULD NOT be interpreted if its
Block
field is anterior to previously recorded Peer document for a samePublicKey
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
- Signatures are to be associated to each
- 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 theIssuers
line count - A transaction must have its issuers appear at least once in the
Inputs
field, where an issuer is linked toINDEX
by its position in theIssuers
field. First issuer isINDEX = 0
. - A transaction's
Inputs
amount sum must be equal to theOuputs
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 document, and then letting peers deal with the API(s) they prefer themselves.
At this stage, only the Duniter HTTP API (named BASIC_MERKLED_API) is known as a valid UCP API.
References
- Relative Theory of the Money, the theoretical reference behind Universal Dividend
- OpenUDC, the inspiration project of Duniter
- Bitcoin, the well known crypto-currency system