Skip to content

Scaffold an initial cardano-crypto-leios package#670

Open
ch1bo wants to merge 14 commits into
masterfrom
ch1bo/cardano-crypto-leios
Open

Scaffold an initial cardano-crypto-leios package#670
ch1bo wants to merge 14 commits into
masterfrom
ch1bo/cardano-crypto-leios

Conversation

@ch1bo

@ch1bo ch1bo commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Adds a new package for leios cryptographic types and operations. This was done in course of IntersectMBO/ouroboros-consensus#2068, I'm currently integrating this with the cardano-ledger master and expect a follow-up PR there.

The digital signature scheme is BLS12-381 and fixed in the module. Contrary to the CIP-164, the certificate does not contain a slot or EbHash anymore. This makes definition in cardano-base a lot easier and in the current block structure design, the "message" against which the certificate is signed would be available from the (block) context in which the certificate is used.

Most importantly, this module contains encoders/decoders for the LeiosCert type including roundtrip and golden tests. This should be enough for the cardano-ledger to use this type confidently in Dijkstra era blocks.

image

There are also property tests about aggregating and verifying certificates. The Committee is part of this package, but how it is selected is deliberately kept out of scope.

@ch1bo ch1bo changed the title WIP: Scaffold an initial cardano-crypto-leios package Scaffold an initial cardano-crypto-leios package Jun 11, 2026
@ch1bo ch1bo force-pushed the ch1bo/cardano-crypto-leios branch from d85df0c to aa227fa Compare June 16, 2026 14:56
@ch1bo ch1bo requested review from lehins and perturbing June 16, 2026 18:10
@ch1bo ch1bo marked this pull request as ready for review June 16, 2026 18:10
@ch1bo ch1bo force-pushed the ch1bo/cardano-crypto-leios branch from bf92c7d to 780e347 Compare June 17, 2026 07:22
@ch1bo ch1bo force-pushed the ch1bo/cardano-crypto-leios branch 2 times, most recently from 1102098 to 25e16ac Compare June 17, 2026 21:21
ch1bo added 12 commits June 18, 2026 11:38
Roundtrip and golden tests for LeiosCert
These are the only means to create and verify leios certificates about a
certain message (a leios vote). Committee selection was deliberately
kept out of scope
The golden test compares 'cardano-crypto-leios/test/golden/LeiosCert'
byte-for-byte against the hex-dump output of 'encodeWithIndex'. Without
this attribute, the default Windows 'core.autocrlf=true' translates LF
to CRLF on checkout and the comparison fails, even though the file is
committed with LF endings.
These were needed/useful in the cardano-ledger-dijkstra integration
@ch1bo ch1bo force-pushed the ch1bo/cardano-crypto-leios branch 2 times, most recently from 73d303e to 745de18 Compare June 18, 2026 19:18
This avoids redundant import warnings on newer GHC versions
@ch1bo ch1bo force-pushed the ch1bo/cardano-crypto-leios branch from 745de18 to 38a3b98 Compare June 18, 2026 19:24

@lehins lehins left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency is one of the most important parts in software development. It is important to use consistent dependencies as the rest of the project, in this case cardano-base repo being that project.

Comment thread cardano-crypto-leios/cardano-crypto-leios.cabal Outdated
Comment thread cardano-crypto-leios/cardano-crypto-leios.cabal Outdated
Comment thread cardano-crypto-leios/cardano-crypto-leios.cabal Outdated
-- skip per-key PoP checks (they use 'uncheckedAggregateVerKeysDSIGN' /
-- 'aggregateSigsDSIGN' under the hood). Passing in unchecked keys defeats
-- the security of the aggregate signature.
newtype Committee = Committee {committeeVoters :: Vector (Weight, LeiosVerificationKey)}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vector is lazy, which will easily lead to space leaks.
Having a lazy tuple as an element will exacerbate this issue.

At the very least I suggest creating a proper data type for the vector element, instead of using a tuple and be really diligent in forcing elements of a vector. Something like that:

Suggested change
newtype Committee = Committee {committeeVoters :: Vector (Weight, LeiosVerificationKey)}
newtype Committee = Committee {committeeVoters :: Vector LeiosVoter}
data LeiosVoter = LeiosVoter
  { leiosVoterWeight :: !Weight
  , leiosVoterVerKey :: !LeiosVerificationKey
  }

= -- | A voter index in the contributions is past the committee bound.
VoterIdOutOfBounds !VoterId
| -- | BLS signature aggregation failed (e.g. malformed input signature).
BLSAggregationFailed !String

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List is lazy. Making it strict in WHNF is not very useful. Therefore it is best to switch to Text

Suggested change
BLSAggregationFailed !String
BLSAggregationFailed !Text


-- | Plain CBOR decoder for 'LeiosCert', matching the CDDL in 'LeiosCert'.
decodeLeiosCert :: Decoder s LeiosCert
decodeLeiosCert = do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This decoder will be unusable in ledger, since it doesn't support indefinite length encoding

aggregateLeiosCert committee contributions = do
let n = committeeSize committee
entries = Map.toAscList contributions
case [v | (v, _) <- entries, fromIntegral (voterIndex v) >= n] of

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a separate binding entries will cause that list to be allocated. However if we use Map directly in both places where it is used then we will tap into list fusion and avoid that list being allocated. Also we need to make sure all fromIntegral in this repo are type annotated

Suggested change
case [v | (v, _) <- entries, fromIntegral (voterIndex v) >= n] of
case [v | v <- Map.keys contributions, fromIntegral @Word32 @Int (voterIndex v) >= n] of

v : _ -> Left (VoterIdOutOfBounds v)
[] -> pure ()
aggSig <-
case aggregateSigsDSIGN (map snd entries) of

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case aggregateSigsDSIGN (map snd entries) of
case aggregateSigsDSIGN (Map.elems contributions) of

Comment on lines +141 to +142
{ signers :: !BitField
, aggregatedSignature :: !LeiosSignature

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty terrible naming, since signers can easily be a local binding anywhere in the cardano-node codebase. I suggest something more descriptive like:

Suggested change
{ signers :: !BitField
, aggregatedSignature :: !LeiosSignature
{ leisCertSigners :: !BitField
, leisCertSignature :: !LeiosSignature
-- ^ Aggregated BLS signature

where
-- The bitfield decoder already enforced @i < n@; if the committee is
-- shorter than the decoder's idea of @n@ we treat it as a malformed cert.
accumSigner voters (w, ks) i = case voters V.!? i of

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid space leaks as much as we can!

Suggested change
accumSigner voters (w, ks) i = case voters V.!? i of
accumSigner voters (!w, !ks) i = case voters V.!? i of

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants