- Prerequisites
- Genesis block
- Main block
- Transaction-related types
- SSC-related types
- Delegation-related types
- Update system related types
-
You should have read the Ouroboros paper.
-
You should be familiar with basic Haskell types (e.g.
Int
) and with some less common Haskell types (e.g.NonEmpty
); when in doubt, use Hoogle. -
Basic knowledge of the concept of secret sharing is required, see the section SSC-related types for some links.
-
We assume that you know about hashing and public key cryptography used in Cardano SL – e.g.
Hash
,PublicKey
,Signature
,RedeemPublicKey
. One additional thing that can be noted here is that aStakeholderId
is a hash (AddressHash
) of a public key. -
We also assume that you know about
AsBinary
. If you don't, a one-sentence explanation is thatAsBinary a
contains a serializeda
. It's intended to be used for types like public keys, where deserialization can't fail (unless the key has a wrong length) but still takes significant time and so we'd like to do it lazily. -
Finally, we assume that you've read Unknown data handling, which explains things like
TxInUnknown
andAttributes
. Whenever you see words “attributes map” or “constructor provided for backwards compatibility”, the linked document is probably relevant.
keywords: GenesisBlock
Genesis blocks are blocks that are created at epoch boundary by nodes. They don't contain any data that can't be deduced from the blockchain, but they are sent over the network anyway because that's more convenient.
A genesis block is implemented as a GenericBlock
:
type GenesisBlock = GenericBlock GenesisBlockchain
It contains the following fields:
-
_gbHeader :: GenesisBlockHeader
– a header -
_gbBody :: Body GenesisBlockchain
– a payload consisting of a list of slot leaders chosen for the current epoch (i.e. the one which begins with this block) -
_gbExtra :: GenesisExtraBodyData
– an attributes map, currently empty (Attributes ()
)
Invariants:
- Body proof in the header must be valid.
keywords: GenesisBlockHeader
; ConsensusData GenesisBlockchain
,
GenesisConsensusData
; ExtraHeaderData GenesisBlockchain
,
GenesisExtraHeaderData
, GenesisHeaderAttributes
A genesis block header is a GenericBlockHeader
:
type GenesisBlockHeader = GenericBlockHeader GenesisBlockchain
It contains the following fields:
-
_gbhPrevBlock :: HeaderHash
– a hash of the previous block's header -
_gbhBodyProof :: BodyProof GenesisBlockchain
– a hash of_gbLeaders
from the payload -
_gbhConsensus :: ConsensusData GenesisBlockchain
– meta-information about the block:_gcdEpoch :: EpochIndex
– the epoch which the block belongs to (a genesis block technically is at the very beginning of an epoch)_gcdDifficulty :: ChainDifficulty
– difficulty of the chain ending in this block (i.e. number of main blocks between the first block ever and this block, inclusive)
-
_gbhExtra :: GenesisExtraHeaderData
– an attributes map, currently empty (Attributes ()
)
keywords: Body GenesisBlockchain
, GenesisBody
; SlotLeaders
The body contains a single non-empty list of slot leaders that were chosen for the current epoch:
data Body GenesisBlockchain = GenesisBody
{ _gbLeaders :: SlotLeaders
}
type SlotLeaders = NonEmpty StakeholderId
keywords: ExtraBodyData GenesisBlockchain
, GenesisExtraBodyData
;
GenesisBodyAttributes
The extra data stored in a genesis block (_gbExtra
) currently contains
only an empty attributes map:
data GenesisExtraBodyData = GenesisExtraBodyData
{ _gebAttributes :: GenesisBodyAttributes
}
type GenesisBodyAttributes = Attributes ()
It might be extended later.
keywords: MainBlock
Main blocks are blocks with actual blockchain-related data (e.g. transactions). Every slot, a single main block is generated by the slot leader and sent to other nodes.
A main block is implemented as a GenericBlock
:
type MainBlock = GenericBlock MainBlockchain
It contains the following fields:
-
_gbHeader :: MainBlockHeader
– a header -
_gbBody :: Body MainBlockchain
– several payloads (transactions, SSC, delegation, and update system) -
_gbExtra :: MainExtraBodyData
– an attributes map, currently empty (Attributes ()
)
Invariants:
-
Header:
- Body proof (
_gbhBodyProof
) must be valid. - Extra body hash (
_mehEBDataProof
) must be valid.
- Body proof (
-
SSC payload:
- Certificates must have valid time-to-live – the protocol defines
values
(vssMinTTL, vssMaxTTL)
, and we check that for each certificate,certExpiryEpoch - blockEpoch + 1
lies in the interval[vssMinTTL; vssMaxTTL]
. - Commitments must have right signatures. To verify commitment's signature, we must know the epoch – therefore it can't be checked as a part of the commitment invariants.
- The commitments should each contain at least one share, and the shares in commitments should be deserializable.
- The payload type should correspond to the slot of the block
(commitments are only allowed in slots
[0;2k)
, openings – in slots[4k;6k)
, and shares – in slots[8k;10k)
).
- Certificates must have valid time-to-live – the protocol defines
values
-
Delegation payload:
- All proxy keys must have the same epoch as the block itself.
keywords: MainBlockHeader
; ConsensusData MainBlockchain
,
MainConsensusData
; MainExtraHeaderData
A main block header is a GenericBlockHeader
:
type MainBlockHeader = GenericBlockHeader MainBlockchain
It contains the following fields:
-
_gbhPrevBlock :: HeaderHash
– a hash of the previous block's header. -
_gbhBodyProof :: BodyProof MainBlockchain
– proofs (e.g. hashes) of payloads from the block body; they will be discussed in the section about block payload. -
_gbhConsensus :: ConsensusData MainBlockchain
– meta-information about the block:_mcdSlot :: SlotId
– the slot for which the block was generated_mcdLeaderKey :: PublicKey
– public key of the slot leader (which may be different from the block issuer, because of delegation; if the issuer isn't the slot leader, the signature will contain the actual issuer)_mcdDifficulty :: ChainDifficulty
– difficulty of the chain ending in this block (i.e. number of main blocks between the first block ever and this block, inclusive)_mcdSignature :: BlockSignature
– a signature of the block by its issuer (if the issuer isn't the slot leader, it will be a delegated signature confirming issuer's right to issue the block in this slot)
-
_gbhExtra :: MainExtraHeaderData
– more information about the block:_mehBlockVersion :: BlockVersion
– the block version; see Software and block versions. Block version can be associated with a set of protocol rules. Rules associated with_mehBlockVersion
from a block are the rules used to create that block (i.e. the block must adhere to these rules)._mehSoftwareVersion :: SoftwareVersion
– the software version (see the same link); the version of software that created the block_mehEBDataProof :: Hash MainExtraBodyData
– a hash of the extra data in the block (since ultimately a header needs to checksum all data in the block)_mehAttributes :: BlockHeaderAttributes
– an attributes map to extend the header with more fields, currently empty
Invariants:
-
The signature must be valid.
-
If the signature is a delegated signature (
BlockPSignatureLight
orBlockPSignatureHeavy
), it must not be self-signed – i.e. thepskIssuerPk
andpskDelegatePk
must be different.
keywords: BlockSignature
, MainToSign
A BlockSignature
verifies that the block was issued by someone who had a
right to issue it, and also verifies that the block hasn't been tampered
with:
data BlockSignature
= BlockSignature (Signature MainToSign)
| BlockPSignatureLight (ProxySigLight MainToSign)
| BlockPSignatureHeavy (ProxySigHeavy MainToSign)
There are three kinds of signatures:
-
BlockSignature
– simply a signature of the block by its issuer -
BlockPSignatureLight
– a light delegation signature (not discussed in this document); this signature is used when the right to issue blocks has been transferred on a temporary basis -
BlockPSignatureHeavy
– a heavy delegation signature, used when the right to issue blocks has been transferred “until further notice”
The thing signed by a BlockSignature
isn't the whole block, but basically
MainBlockHeader
without the BlockSignature
. We avoid signing the whole
block because signing is an expensive process and we'd like to sign as few
bytes as we can get away with; since a MainBlockHeader
already contains
hashes of all parts of the block, it works out. The exact parts that are
signed are specified by MainToSign
:
data MainToSign = MainToSign
{ _msHeaderHash :: HeaderHash -- previous block's header
, _msBodyProof :: BodyProof MainBlockchain -- hashes of block's body
, _msSlot :: SlotId -- current slot
, _msChainDiff :: ChainDifficulty -- difficulty
, _msExtraHeader :: MainExtraHeaderData -- extra data from the header
}
The choice of signed data ensures that the whole block can be verified given
a BlockSignature
.
keywords: Body MainBlockchain
, MainBody
; BodyProof MainBlockchain
,
MainProof
The block body consists of four payloads – transactions, SSC, delegation, and update system. In further sections we discuss each of those payloads, as well as corresponding proofs (stored in the header).
data Body MainBlockchain = MainBody
{ _mbTxPayload :: TxPayload
, _mbSscPayload :: SscPayload
, _mbDlgPayload :: DlgPayload
, _mbUpdatePayload :: UpdatePayload
}
keywords: TxPayload
, TxProof
The transaction payload consists of a Merkle tree with transactions, and a list of witnesses corresponding to those transactions:
data TxPayload = UnsafeTxPayload
{ _txpTxs :: MerkleTree Tx -- transactions
, _txpWitnesses :: [TxWitness] -- witnesses
}
The invariant is that the tree of transactions and the list of witnesses have the same number of elements.
The reason we separate transactions and their witnesses is that light clients might want to request transactions but not witnesses (since they don't have enough information to verify the witnesses anyway), and- in order to be able to verify the acquired list of transactions given only the block header, we need a hash of transactions (separated from witnesses) in the header.
The proof of TxPayload
looks like this:
data TxProof = TxProof
{ txpNumber :: Word32 -- number of transactions
, txpRoot :: MerkleRoot Tx -- root of the transaction tree
, txpWitnessesHash :: Hash [TxWitness] -- hash of the witnesses list
}
-
The integrity of the transaction tree is ensured by recording the size and root of the tree; the root of a Merkle tree is a hash which depends on all other elements, and the shape of the tree is uniquely determined by the number of elements in the tree.
-
The integrity of the witnesses list is ensured by recording its hash.
keywords: SscPayload
, SscProof
, VssCertificatesHash
The SSC payload always contains a certificates map (VssCertificatesMap
)
and can optionally contain exactly one of these: a commitments map, an
openings map, or a shares map. You should see the section
about SSC-related types if you want to know what
purpose they serve.
data SscPayload
= CommitmentsPayload
!CommitmentsMap
!VssCertificatesMap
| OpeningsPayload
!OpeningsMap
!VssCertificatesMap
| SharesPayload
!SharesMap
!VssCertificatesMap
| CertificatesPayload
!VssCertificatesMap
This type has no invariants.
The proof of SscPayload
simply consists of hashes of its fields:
data SscProof
= CommitmentsProof
!(Hash CommitmentsMap)
!VssCertificatesHash
| OpeningsProof
!(Hash OpeningsMap)
!VssCertificatesHash
| SharesProof
!(Hash SharesMap)
!VssCertificatesHash
| CertificatesProof
!VssCertificatesHash
Instead of Hash VssCertificatesMap
we use a VssCertificatesHash
, which
is defined differently:
type VssCertificatesHash = Hash (HashMap StakeholderId VssCertificate)
The reason for that is that hashing is done after serialization, and at some
point the serialization format for VssCertificatesHash
was changed from a
map to a set. Since we can't change the protocol easily at this point, for
hashing we still use the map representation (where the map key corresponding
to each certificate is a StakeholderId
derived from its the certificate's
vcSigningKey
).
keywords: DlgPayload
The delegation payload is a list of proxy keys. The invariant is that no two
proxy keys have the same issuer (pskIssuerPk
).
newtype DlgPayload = UnsafeDlgPayload [ProxySKHeavy]
The proof used for this type is simply a Hash DlgPayload
.
keywords: UpdatePayload
, UpdateProof
The update system payload contains a list of votes and (optionally) an update proposal.
data UpdatePayload = UpdatePayload
{ upProposal :: Maybe UpdateProposal
, upVotes :: [UpdateVote]
}
This type has no invariants.
The proof used for this type is simply a Hash UpdatePayload
.
keywords: Tx
, TxId
, TxIn
, TxOut
, TxAttributes
A transaction (Tx
) can be thought of as a command to destroy several
unspent outputs and create new unspent outputs, possibly with different
owners. In this fashion, money can be transferred from one person to another
by destroying money belonging to the first person and creating money that
would belong to the second person. (See Transactions in Cardano SL for
more details.)
Here is the definition of a transaction:
data Tx = UnsafeTx
{ _txInputs :: NonEmpty TxIn -- inputs
, _txOutputs :: NonEmpty TxOut -- outputs
, _txAttributes :: TxAttributes -- attributes
}
The invariant is that all outputs must have value bigger than 0. Two
additional invariants (“there's at least one input” and “there's at least
one output”) are always satisfied thanks to using NonEmpty
.
Transactions are referred to by their hashes:
type TxId = Hash Tx
Inputs are represented like this: since currently unspent outputs can only originate from transactions, any unspent output can be referred to by specifying a transaction ID and index in the list of that transaction's outputs:
data TxIn
= TxInUtxo -- an output of some other transaction
{ txInHash :: TxId
, txInIndex :: Word32
}
| TxInUnknown Word8 ByteString -- for backwards compatibility
Outputs themselves are represented like this:
data TxOut = TxOut
{ txOutAddress :: Address -- the owner of the output
, txOutValue :: Coin -- amount of money
}
keywords: Address
, AddrType
, AddrSpendingData
An address (Address
) is something that can be an output of a transaction
(i.e. something we can send coins to). The structure of an Address
is
complicated due to several constraints placed on its design. This section
starts with discussing several smaller types, and then looks at Address'
and finally Address
.
First of all, there are three kinds of addresses:
data AddrType
= ATPubKey -- pay to public key
| ATScript -- pay to script
| ATRedeem -- pay to redeem public key
| ATUnknown Word8 -- (for backwards compatibility)
For each of those kinds, there is a corresponding constructor of
AddrSpendingData
– a type that specifies the condition which somebody has
to satisfy in order to spend the funds:
data AddrSpendingData
= PubKeyASD PublicKey
| ScriptASD Script
| RedeemASD RedeemPublicKey
| UnknownASD Word8 ByteString -- for backwards compatibility, too
-
PubKeyASD pubkey
– you have to provide a signature by given key -
ScriptASD validator
– you have to provide a redeemer script that will match the validating script (see Witness for more details about what it means) -
RedeemASD pubkey
– you have to provide a signature by given redeem key
These two types are stored in an Address'
along with address attributes:
newtype Address' =
Address' (AddrType, AddrSpendingData, Attributes AddrAttributes)
Address's attributes store the derivation path (not explained in this
document) and stake distribution. When money is sent to an address, we take
only the distribution into account for the purpose of choosing slot leaders;
it doesn't matter who the money actually belongs to (as determined by
AddrSpendingData
).
data AddrAttributes = AddrAttributes
{ aaPkDerivationPath :: Maybe HDAddressPayload
, aaStakeDistribution :: AddrStakeDistribution
}
data HDAddressPayload = HDAddressPayload ByteString
data AddrStakeDistribution
= BootstrapEraDistr
| SingleKeyDistr StakeholderId
| UnsafeMultiKeyDistr (Map StakeholderId CoinPortion)
-- ^ Stake distribution which gives stake to multiple
-- stakeholders. 'CoinPortion' is a portion of an output (output
-- has a value, portion of this value is stake). The constructor
-- is unsafe because there are some predicates which must hold:
-- • the sum of portions must be @maxBound@ (basically 1);
-- • all portions must be positive;
-- • there must be at least 2 items, because if there is only one item,
-- 'SingleKeyDistr' can be used instead (which is smaller).
newtype CoinPortion = CoinPortion Word64
There are three available distributions:
-
BootstrapEraDistr
– stake is assigned to bootstrap era stakeholders -
SingleKeyDistr id
– all stake is assigned to the stakeholder with given ID -
UnsafeMultiKeyDistr map
– stake is divided between several stakeholders. For each stakeholder there's provided aCoinPortion
– the dole of the stake that should be assigned to that stakeholder.
Invariants of UnsafeMultiKeyDistr
:
- The sum of portions must be 1.
- All portions must be positive (i.e. not 0).
- There must be at least two stakeholders.
A CoinPortion
is a newtype for Word64
, and is interpreted as the
numerator of x / 10^15
. Its invariant is that the x
must lie in the
interval [0; 10^15]
.
An Address'
provides enough information to receive funds. However, a
problem with Address'
is that it reveals public keys, which is something
we don't want to do (to stay safe if public key cryptography gets broken in
the future). Therefore we wrap Address'
into a structure that stores
Address'
's hash and all data from Address'
except for
AddrSpendingData
:
type AddressHash = AbstractHash Blake2b_224
data Address = Address
{ addrRoot :: AddressHash Address'
, addrAttributes :: Attributes AddrAttributes
, addrType :: AddrType
}
At this point a perceptive reader could note that we could accomplish the
same goal in a simpler manner – just store a hash of AddrSpendingData
and
dispose of Address'
entirely. However, it would mean that when you give
your address to someone, they can easily change its AddrAttributes
and the
address would still be valid. This gives rise to certain legal problems. For
instance, if you are a shop and you've been paid to an address that has had
its AddrAttributes
modified, you can still use the money but you might no
longer have stake assigned to you – does that constitute a valid payment or
not?
keywords: TxWitness
, TxInWitness
, TxSig
, TxSigData
A transaction witness (TxWitness
) is a proof which authorizes spending
each of the inputs of the transaction.
(See Proofs of transaction legitimacy for more details.) A TxWitness
is a list of input witnesses:
type TxWitness = Vector TxInWitness
There is a separate type of input witnesses (TxInWitness
) for each
AddrType
:
data TxInWitness
= PkWitness
{ twKey :: PublicKey -- key corresponding to the address
, twSig :: TxSig -- a signature by that key
}
| ScriptWitness
{ twValidator :: Script -- validator
, twRedeemer :: Script -- redeemer
}
| RedeemWitness
{ twRedeemKey :: RedeemPublicKey -- key
, twRedeemSig :: RedeemSignature TxSigData -- signature
}
| UnknownWitnessType Word8 ByteString
-
A
PkWitness
provides a public key (which is checked to correspond to the key hash stored in address) and a transaction signature by that key. Specifically, we sign aHash
of the whole transaction:type TxSig = Signature TxSigData data TxSigData = TxSigData { txSigTxHash :: Hash Tx }
-
A
ScriptWitness
provides a pair of scripts –twValidator
is checked to correspond to the hash in the address and then the pair is executed together and if it returnsTrue
, the input is considered valid. -
A
RedeemWitness
is similar to aPkWitness
, but it's only used for redemption. -
An
UnknownWitnessType
is used for providing backwards compatibility.
keywords: Script
, ScriptVersion
A transaction script (Script
) is a collection of definitions written
in Plutus. It can be deserialized and loaded, and any definition in it
can be evaluated.
data Script = Script
{ scrVersion :: ScriptVersion -- version of Plutus to use
, scrScript :: ByteString -- serialized script
}
type ScriptVersion = Word16
scrScript
doesn't necessarily have to be a valid script (if it can't be
deserialized, it will be handled on later stages).
keywords: MerkleTree
, MerkleNode
, MerkleRoot
A Merkle tree is a binary tree where each leaf contains a value, and each node contains a hash that depends on hashes of its two children. For convenience, we define a newtype for hashes used in Merkle trees:
newtype MerkleRoot a = MerkleRoot (Hash Raw)
For the tree itself, we use two types:
-
MerkleNode
– a simple, recursively defined type -
MerkleTree
– a wrapper that has a field for tree size and also provides a way to denote an empty tree
data MerkleTree a
= MerkleEmpty -- empty tree
| MerkleTree Word32 (MerkleNode a) -- non-empty tree, with size
data MerkleNode a
-- a branch with two children
= MerkleBranch
{ mRoot :: MerkleRoot a -- combined hash
, mLeft :: MerkleNode a -- left subtree
, mRight :: MerkleNode a -- right subtree
}
-- a single leaf
| MerkleLeaf
{ mRoot :: MerkleRoot a -- hash of the element
, mVal :: a -- the element stored in the leaf
}
Our implementation follows RFC 6962, except that we use a different hash instead of SHA-256. Read it to understand how exactly a tree is built and how branches' hashes are computed from children's hashes.
This section assumes that you are familiar with Ouroboros. You may also
want to read PVSS Implementation in Cardano SL to understand how the
internals of Commitment
, Opening
, etc are implemented, and see the
sources of the pvss-haskell
library.
keywords: VssPublicKey
, VssKeyPair
SSC uses a different public key cryptography scheme. Instead of PublicKey
and SecretKey
it uses VssPublicKey
and VssKeyPair
:
newtype VssPublicKey = VssPublicKey Scrape.PublicKey
newtype VssKeyPair = VssKeyPair Scrape.KeyPair
keywords: VssCertificate
, VssCertificatesMap
Since everywhere else in Cardano we use PublicKey
and SecretKey
, we need
to be able to establish a correspondence between stakeholders' public keys
and their VSS keys. This is done by publishing a certificate
(VssCertificate
):
data VssCertificate = UnsafeVssCertificate
{ vcVssKey :: AsBinary VssPublicKey
, vcExpiryEpoch :: EpochIndex
, vcSignature :: Signature (AsBinary VssPublicKey, EpochIndex)
, vcSigningKey :: PublicKey
}
The fields mean:
-
vcVssKey
– stakeholder's VSS key -
vcSigningKey
– stakeholder's public key -
vcExpiryEpoch
– the epoch up to which the certificate is considered valid -
vcSignature
– a signature of(vcVssKey, vcExpiryEpoch)
The invariant of VssCertificate
is that the signature must be valid.
A certificates map (VssCertificatesMap
) is a set of certificates, indexed
by stakeholder IDs (hashes of certificates' vcSigningKey
s) for
performance. There are two invariants:
- The IDs must indeed correspond to certificates' keys.
- No two certificates can have the same
vcVssKey
.
newtype VssCertificatesMap =
UnsafeVssCertificatesMap (HashMap StakeholderId VssCertificate)
keywords: Commitment
, CommitmentSignature
, SignedCommitment
,
CommitmentsMap
, SecretProof
After an SSC participant generates a secret, it also generates a
Commitment
:
data Commitment = Commitment
{ commProof :: SecretProof -- proof of secret
, commShares :: HashMap (AsBinary VssPublicKey) -- encrypted shares
(NonEmpty (AsBinary EncShare))
}
The fields mean:
-
commShares
– a number of encrypted shares, which can be decrypted and recovered back into a secret. For each participant's VSS key theHashMap
lists one or more shares encrypted with that participant's key. They can only be decrypted with the correspondingVssKeyPair
. -
commProof
– a proof, for which it is possible to check that a) it corresponds to the encrypted shares, and b) a certain secret corresponds or doesn't correspond to it.
By itself a commitment isn't signed – there's a separate type called
SignedCommitment
:
type SignedCommitment = (PublicKey, Commitment, CommitmentSignature)
type CommitmentSignature = Signature (EpochIndex, Commitment)
This is similar to how signing a VssCertificate
works, except for one
thing: each commitment is only valid for one epoch, and we always know what
epoch it is, so we don't have to include the epoch into SignedCommitment
.
A commitments map (CommitmentsMap
) is a set of signed commitments, indexed
by stakeholder IDs. The invariant is that the IDs must correspond to
commitments' PublicKey
s.
newtype CommitmentsMap =
CommitmentsMap (HashMap StakeholderId SignedCommitment)
The implementation of SecretProof
is given below, but it will not be
explained here as it's out of scope for this document.
data SecretProof = SecretProof
{ spExtraGen :: Scrape.ExtraGen
, spProof :: Scrape.Proof
, spParallelProofs :: Scrape.ParallelProofs
, spCommitments :: [Scrape.Commitment]
}
keywords: Opening
, Secret
, OpeningsMap
An opening is sent at the second stage of SSC. It reveals the participant's secret (which can be thought of as a simple bytestring).
newtype Opening = Opening (AsBinary Secret)
An openings map (OpeningsMap
) is a map from participants' IDs to their
openings. It has no invariants (since the opening doesn't have a public key
in it).
type OpeningsMap = HashMap StakeholderId Opening
keywords: EncShare
, DecShare
; SharesMap
, InnerSharesMap
A share is a part of a secret (see Wikipedia on secret sharing). With
more than a certain percentage of decrypted shares (DecShare
) it is
possible to recover the secret that was used to create the shares. An
encrypted share (EncShare
) can be decrypted with the corresponding VSS
keypair.
newtype DecShare = DecShare Scrape.DecryptedShare
newtype EncShare = EncShare Scrape.EncryptedSi
A shares map (SharesMap
) is a set of shares created by some participants
and decrypted by other participants:
- outer key = who decrypted the share
- inner key = who created the share
type SharesMap = HashMap StakeholderId InnerSharesMap
type InnerSharesMap = HashMap StakeholderId (NonEmpty (AsBinary DecShare))
So, an InnerSharesMap
contains all shares decrypted by a certain
participant, and keys of InnerSharesMap
denote who created some particular
set of shares.
Note: this section talks about heavy delegation only.
keywords: ProxySecretKey
, ProxyCert
, ProxySKHeavy
A proxy key (ProxySecretKey w
) is a PublicKey
, stored in
pskDelegatePk
, that comes equipped with three things:
-
pskOmega
– a value of typew
-
pskIssuerPk
– a key which delegated something (e.g. block issuing rights) topskDelegatePk
-
pskCert
– a proof thatpskIssuerPk
has delegated topskDelegatePk
data ProxySecretKey w = ProxySecretKey
{ pskOmega :: w -- auxiliary value
, pskIssuerPk :: PublicKey -- delegation happens from this key
, pskDelegatePk :: PublicKey -- ...to this key
, pskCert :: ProxyCert w -- a proof by 'pskIssuerPk'
}
A ProxyCert
is just a signature of (pskDelegatePk, pskOmega)
:
newtype ProxyCert w = ProxyCert { unProxyCert :: CC.XSignature }
Note that “proxy secret key” is a misnomer – it really is a public key, not
a secret key. Also note that it might look like ProxySecretKey
has an
invariant (“the certificate in it is valid”), but in reality this isn't an
invariant, it's simply something we check in code at certain points.
Now that we have ProxySecretKey w
, we can specify different w
s and get
different things. For heavy delegation we set w = EpochIndex
:
type ProxySKHeavy = ProxySecretKey EpochIndex
So, a proxy delegation key contains the epoch when it was issued. This is needed to prevent replay attacks – if later on the proxy key gets revoked, the attacker wouldn't be able to simply resend the original key.
keywords: ProxySignature
, ProxySigHeavy
A ProxySignature w a
is a signature of a
by a ProxySecretKey w
:
data ProxySignature w a = ProxySignature
{ psigPsk :: ProxySecretKey w
, psigSig :: XSignature
}
Just like before, for heavy delegation we set w = EpochIndex
:
type ProxySigHeavy a = ProxySignature EpochIndex a
The reason psigPsk
is included into a proxy signature is that we need to
know who was the actual signer to check the singature.
keywords: UpdateData
An UpdateData
contains information that is needed to actually update from
one version of the application to another. For each supported OS we need a
different UpdateData
.
data UpdateData = UpdateData
{ udAppDiffHash :: Hash Raw
, udPkgHash :: Hash Raw
, udUpdaterHash :: Hash Raw
, udMetadataHash :: Hash Raw
}
The fields mean:
-
upAppDiffHash
– hash of binary diff between two applications. This diff can be used for directly patching binary files on user's computer. -
udPkgHash
– hash of an installer that can be used to install the new version from application from scratch instead of patching the existing application. -
udUpdaterHash
– currently unused. -
udMetadataHash
– currently unused.
(Note that an UpdateData
contains only hashes – the diffs/installers can
be queried by hash from update servers or via P2P.)
keywords: UpdateProposal
, UpdateProposalToSign
, UpAttributes
,
BlockVersion
, BlockVersionModifier
, SoftwareVersion
, SystemTag
An update proposal (UpdateProposal
) is a complete description of a client
update published on the blockchain.
data UpdateProposal = UnsafeUpdateProposal
{ upBlockVersion :: BlockVersion
, upBlockVersionMod :: BlockVersionModifier
, upSoftwareVersion :: SoftwareVersion
, upData :: HashMap SystemTag UpdateData
, upAttributes :: UpAttributes
, upFrom :: PublicKey
, upSignature :: Signature UpdateProposalToSign
}
The fields mean:
-
upBlockVersion
,upBlockVersionMod
,upSoftwareVersion
– see Software and block versions -
upData
– data required to perform the update on different systems; this map can be empty if the update proposal only changes some constants and doesn't require modifying the application's binaries -
upAttributes
— the attributes map, currently empty (i.e.Attributes ()
) -
upFrom
– the public key of whoever proposed theUpdateProposal
-
upSignature
– a signature ofUpdateProposalToSign
(a type containing all fields ofUpdateProposal
except forupFrom
andupSignature
) by theupFrom
key
Invariant: upSignature
is valid.
The SystemTag
type is used for identifying different systems. It is a
synonym for Text
(a sample value could be e.g. "macos64"
):
newtype SystemTag = SystemTag Text
There are two associated invariants:
-
It may only contain ASCII symbols.
-
It may not be longer than 10 characters.
keywords: UpdateVote
, UpId
Stakeholders may vote for published proposals. A vote can be either positive or negative. The type for votes is given below:
data UpdateVote = UpdateVote
{ uvKey :: PublicKey -- public key of the voter
, uvProposalId :: UpId -- proposal which is voted for
, uvDecision :: Bool -- whether the vote is positive
, uvSignature :: Signature (UpId, Bool) -- a signature by 'uvKey'
}
Proposals are identified simply by their hashes:
type UpId = Hash UpdateProposal
The invariant of UpdateVote
is that the signature is valid.