We propose to integrate Mithril Signer and Aggregator nodes in a non-intrusive way side by side Cardano node in order to be able to use the Cardano network layer for Mithril inter-node communications.
In this way, we can significantly reduce the cost and effort required to build a decentralised network for Mithril by using Cardano's established infrastucture, with negligible to no impact on the performance and no impact on the security of the Cardano network.
Mithril is a protocol based on Stake-based Threshold Multisignatures scheme which efficiently produces certificates which can be used in a trustless way. There are several usecases envisaged for the Cardano ecosystem such as fast bootstrapping of full nodes, enabling secure light wallets, and decentralised voting.
There are several key components and requirements that need to be in place:
Such a network, if built from the ground up, would require significant efforts and investment. Furthermore, the majority of SPO's, as the representatives of Cardano's active stake, will have to adopt and operate Mithril nodes alongside their Cardano node. This is not guaranteed without a sustainable incentive scheme in place due to the additional maintenance and operational costs. Mithril is a lightweight protocol with respect to both computation and network communication overhead and has less strict network guarantees requirements than Cardano consensus. Thus a natural solution is to use the Cardano network layer to significantly facilitate the development of Mithril protocol without a significant impact on the Cardano network or high maintenance efforts for the SPOs. The proposed solution is described in detail below.
This specification proposes to create 4
new mini-protocols in the Cardano network layer:
node-2-node
mini-protocols:
node-2-client
mini-protocols:
This protocol is run in the node-2-node stack.
This is the protocol driving the diffusion of signers registration across the network. The purpose of this protocol is to ensure timely diffusion of each signer's registration for the next epoch.
How do we avoid last minute registrations from malicious players? Even if we have a time window for registering, we still need to allow for some marging so that messages reach the full network. But what stops adversaries from sending a registration request off-limits of the time-window? This is important, because if honest nodes have registrations with different users, their signatures will not be compatible for aggregation.
There's a need to limit the time range during which we allow for registration to be sent and to be received, as we want to avoid last minute flooding of registrations that would create discrepancy between signers that would prevent them from signing.
In order to achieve consensus on the signer registrations, a solution could be having the SPOs create a transaction on the main chain to register their Mithril verification keys (once every epoch or k
epochs if a KES like mechanism is possible).
haveSignersFor?(range = [0, 100])
hasSigners!( list = [id_x, id_y, ..., id_z])
-> giveMeSigners(list = [id_x, id_y])
<- signer(signer = id_x, registration = "0xcafe")
<- signer(signer = id_y, registration = "0xcafe")
...
<- noMoreSigners
We expect this protocol to guarantee that if a node has emitted a signer registration for a given epoch, it will reach all signers before the end of the epoch. Or in other words, the set of registered signers must be identical for each signer node.
The pull system should ensure convergence of state for all involved nodes within the timespan of an epoch.
There is a delicate topic regarding the collection of signers' registration:
There is a need to define openWindow
and closeWindow
, which corresponds to the time we allow for registration (or when we decide to stop accepting registrations). These values could be tied to epoch numbers, or the security parameter k
.
This problem is not trivial, and by no means resolved. We should check with the rest of the team (consensus/ledger/network) whether it makes sense to treat registration requests in a similar way as blocks are treated (e.g. registration pool waiting to be included in a 'RegistrationBlock'). If we treat them as such, then the 'k' parameter is again relevant for agreeing on 'when' a fork can no longer happen.
Agency | |
---|---|
Client has Agency | StIdle, StSyncing |
Server has Agency | StFindSigners, StStreamingSigners |
From state | Message | Parameters | To State |
---|---|---|---|
StIdle | MsgOpenWindow | StSyncing | |
StSyncing | MsgRequestRange | range | StFindSigners |
StFindSigners | MsgIndexList | list | StSyncing |
StSyncing | MsgGetSignersAtIndexes | list | StStreamingSigners |
StStreamingSigners | MsgSigner | signer_registration | StStreamingSigners |
StStreamingSigners | MsgNoMoreSigners | StSyncing | |
StSyncing | MsgCloseWindow | StIdle | |
StIdle | MsgClientDone | StDone |
1
2 signerRegistrationMessage
3 = msgOpenWindow
4 / msgCloseWindow
5 / msgRequestRange
6 / msgIndexesList
7 / msgGetSignersAtIndexes
8 / msgSigner
9 / msgNoMoreSigners
10 / msgClientDone
11
12 msgOpenWindow = [ 0 ]
13 msgCloseWindow = [ 1 ]
14 msgRequestRange = [ 2, point , point ]
15 msgIndexesList = [ 3, * point]
16 msgGetSignersAtIndexes = [ 4 , * point ]
17 msgSigner = [ 5, signer_registration ]
18 msgNoMoreSigners = [ 6 ]
19 msgClientDone = [ 7 ]
20
21 signer_registration =
22 [ registration_header
23 , party_index: word16
24 , party_id : bech32
25 , verification_key : base64
26 , verification_key_signature : base64
27 , operational_certificate : base64
28 ]
This protocol is run in the node-2-node stack.
This is the protocol driving the diffusion of signers signatures across the network. The purpose of this protocol is to ensure timely diffusion of each signer's signature for the current signing round.
A signing round is an event that is defined as:
haveSignaturesFor?(range = [0, 100])
hasSignatures!( list = [id_x, id_y, ..., id_z])
-> giveMeSignatures(list = [id_x, id_y])
<- signature(signer = id_x, signature = "0xcafe")
<- signature(signer = id_y, signature = "0xcafe")
...
<- noMoreSignatures
Agency | |
---|---|
Client has Agency | StIdle |
Server has Agency | StFindSignatures, StStreamingSignatures |
From state | Message | Parameters | To State |
---|---|---|---|
StIdle | MsgRequestRange | range | StFindSignatures |
StFindSignatures | MsgIndexList | list | StIdle |
StIdle | MsgGetSignaturesAtIndexes | list | StStreamingSignatures |
StStreamingSignatures | MsgSignature | signer_signature | StStreamingSignatures |
StStreamingSignatures | MsgNoMoreSignatures | StIdle | |
StIdle | MsgClientDone | StDone |
1
2 signerSignatureMessage
3 = msgRequestRange
4 / msgIndexesList
5 / msgGetSignaturesAtIndexes
6 / msgSignature
7 / msgNoMoreSignatures
8 / msgClientDone
9
10 msgRequestRange = [ 0, point , point ]
11 msgIndexesList = [ 1, * point]
12 msgGetSignaturesAtIndexes = [ 2 , * point ]
13 msgSignature = [ 3, signer_signature ]
14 msgNoMoreSignatures = [ 4 ]
15 msgClientDone = [ 5 ]
16
17 signer_signature =
18 [ signature_header
19 , party_index: word16
20 , single_signature : base64
21 , won_lotteries : * point
22 ]
This protocol is run in the node-2-client stack.
This is the protocol used by signers to broadcast their registration and signatures. Note that this protocol allows a signer to send both their registration for a given epoch and their signature for a given signing round.
Agency | |
---|---|
Client has Agency | StIdle, StDone |
Server has Agency | StRegistering, StSigning |
From state | Message | Parameters | To State |
---|---|---|---|
StIdle | MsgSubmitRegistration | signer_registration | StRegistering |
StRegistering | MsgRegistrationSuccessful | StDone | |
StRegistering | MsgRegistrationError | error | StDone |
StIdle | MsgSubmitSignature | signer_signature | StSigning |
StSigning | MsgSignatureSuccessful | StDone | |
StSigning | MsgSignatureError | error | StDone |
StIdle | MsgClientDone | StDone |
1
2 localSubmissionMessage
3 = msgSubmitRegistration
4 / msgRegistrationSuccessful
5 / msgRegistrationError
5 / msgSubmitSignature
6 / msgSignatureSuccessful
7 / msgSignatureError
8 / msgClientDone
9
10 msgSubmitRegistration = [ 0, signer_registration ]
11 msgRegistrationSuccessful = [ 1 ]
12 msgRegistrationError = [ 2 , error ]
13 msgSubmitSignature = [ 3, signer_signature ]
14 msgSignatureSuccessful = [ 4 ]
15 msgSignatureError = [ 5 , error ]
16 msgClientDone = [ 6 ]
17
18 signer_registration =
19 [ registration_header
20 , party_index: word16
21 , party_id : bech32
22 , verification_key : base64
23 , verification_key_signature : base64
24 , operational_certificate : base64
25 ]
26
27 signer_signature =
28 [ signature_header
29 , party_index: word16
30 , single_signature : base64
31 , won_lotteries : * point
32 ]
33
34 error =
35 [ error_message : tstr]
33
This protocol is run in the node-2-client stack.
This is the protocol used by signers and aggregators to be notified of registration and signatures of signers.
MsgNoSignature
if there aren't more signatures.Agency | |
---|---|
Client has Agency | StIdle, StStop |
Server has Agency | StHasSignature, StWaitSignature, StHasRegistration, StWaitRegistration |
The following state machine does not represent the full semantics of the Mithril protocol, which requires to collect registrations for an epoch before collecting signatures for the next epoch.
From state | Message | Parameters | To State |
---|---|---|---|
StIdle | MsgNextRegistration | StHasRegistration | |
StHasRegistration | MsgGotRegistration | signer_registration | StIdle |
StHasRegistration | MsgNoRegistration | StWaitRegistration | |
StWaitRegistration | MsgGotRegistration | signer_registration | StIdle |
StWaitRegistration | MsgTimeout | StIdle | |
StIdle | MsgNextSignature | StHasSignature | |
StHasSignature | MsgGotSignature | signer_signature | StIdle |
StHasSignature | MsgNoSignature | StWaitSignature | |
StWaitSignature | MsgGotSignature | signer_signature | StIdle |
StWaitSignature | MsgTimeout | StIdle | |
StIdle | MsgClientDone | StStop |
1
2 localNotificationMessage
3 = msgNextRegistration
4 / msgGotRegistration
5 / msgNoRegistration
5 / msgNextSignature
6 / msgGotSignature
7 / msgNoSignature
8 / msgTimeout
8 / msgClientDone
9
10 msgNextRegistration = [ 0 ]
11 msgGotRegistration = [ 1 ]
12 msgRegistrationError = [ 2 , signer_registration ]
13 msgNoRegistration = [ 3 ]
14 msgNextSignature = [ 4 ]
15 msgGotSignature = [ 5 , signer_signature ]
16 msgNoSignature = [ 6 ]
17 msgTimeout = [ 7 ]
18 msgClientDone = [ 8 ]
19
20 signer_registration =
21 [ registration_header
22 , party_index: word16
23 , party_id : bech32
24 , verification_key : base64
25 , verification_key_signature : base64
26 , operational_certificate : base64
27 ]
28
29 signer_signature =
30 [ signature_header
31 , party_index: word16
32 , single_signature : base64
33 , won_lotteries : * point
34 ]
35
Mithril requires strong network foundations to support interactions between its various nodes:
Why it would be great for Cardano to support Mithril within its network?
15-20x
faster with Mithril).What would be the overhead of operating a Mithril node on the Cardano network?
10 signing rounds
a day).3,500
) per epoch
(each less than 10KB
): this means at most ~35MB / epoch / Cardano node
.3,500
) per signing round
(each less than 1KB
): this means at most ~3.5MB / signing round / Cardano node
or ~175MB / epoch / Cardano node
.10 signing rounds
a day).A hard-fork of the Cardano chain is not required during this implementation plan.
This CIP is licensed under Apache-2.0