-
Notifications
You must be signed in to change notification settings - Fork 42
Perturbing/add msm bls #514
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Perturbing/add msm bls #514
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't looked into ffi calls, just at the haskell portion of it.
I'll look into this in more depth once PR is out of draft
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
Thank you for taking a first look @lehins, much appreciated! The FFI is not working yet, I am getting some segmentation faults that I am trying to debug with valgrind. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have quickly looked through the PR, but haven't spotted exactly what is wrong with this PR, but there is definitely something seriously wrong with memory management in this PR. All the non-deterministic test failures, which should not be the case with functionality that pretends to be "pure" serve as a good indicator that the functionality has a bug. Moreover, the fact that unrelated tests are affected is another strong indicator that something is seriously wrong!
I'll try to dig deeper into this functionality some time this week. Maybe I can help you get to the bottom of this.
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
…1/Internal.hs Co-authored-by: Alexey Kuleshevich <[email protected]>
…1/Internal.hs Co-authored-by: Alexey Kuleshevich <[email protected]>
…1/Internal.hs Co-authored-by: Alexey Kuleshevich <[email protected]>
…1/Internal.hs Co-authored-by: Alexey Kuleshevich <[email protected]>
…1/Internal.hs Co-authored-by: Alexey Kuleshevich <[email protected]>
Co-authored-by: Alexey Kuleshevich <[email protected]>
Co-authored-by: Alexey Kuleshevich <[email protected]>
Co-authored-by: Alexey Kuleshevich <[email protected]>
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
e51ee19
to
53daf8e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I am ready for another round of review (see also the PR description for an overview of the changes).
Thanks in advance, much appreciated.
I think it's best to not yet merge this PR, best to first let @kwxm implement it in plutus via an SRP stanza.
04f0f30
to
03c0c4e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly good to me.
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
withScalarArray scalars go = do | ||
let numScalars = length scalars | ||
sizeReference = sizeOf (undefined :: Ptr ()) | ||
-- Allocate space for the scalars and a null terminator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the null terminator? AFAICT, all code that consumes this API uses an explicit length to determine the end of the array; the null terminator is never used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And to add to that, there are more things that are implicit in for the blst lib that I discovered. For example, for windows builds, given one pair for an input (so one point + scalar), the msm call will also fail. Which Is why I added this
[(scalar, pt)] -> do
i <- scalarToInteger scalar
return (blsMult pt i)
And to test it, see this and this failure on all windows tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is pretty surprising to see null termination. However, unlike usage of null termination for binary data or strings using, for an array of pointers I think it is fairly safe. What BLST designers should have done, is to produce some sort of error when number of pointers does not match where null termination is placed, since both carry the same information and there is a chance for a discrepancy.
It would be good to learn the expected semantics here. For example this would sound sensible, but I can't find any information on it:
- when there is a null pointer in a position that resulted in less points than was expected that it is safe, since one of those points could contain a null pointer
- when number of points (
numScalars
) supplied is reached and the next element in the array is not a null pointer than it is an error.
On a separate subject same comment as I made on withPointArray
applies here too
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
cardano-crypto-class/src/Cardano/Crypto/EllipticCurve/BLS12_381/Internal.hs
Outdated
Show resolved
Hide resolved
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
…1/Internal.hs Co-authored-by: Tobias Dammers <[email protected]>
… one point/scalar to pippenger c call
…it length parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had some questions and minor suggestions, but other than that it is looking good.
Needs a rebase on master
. Also could you please add all visible changes to the Changelog and bump minor version in both cabal file and in the latest section in the changlog file to 2.2.3.0
.
let accumulate [] = do | ||
poke (ptr `advancePtr` numPoints) nullPtr | ||
go numPoints (PointArrayPtr (castPtr ptr)) | ||
accumulate ((ix, point) : rest) = | ||
withPoint point $ \(PointPtr pPtr) -> do | ||
poke (ptr `advancePtr` ix) pPtr | ||
accumulate rest | ||
in accumulate (zip [0 ..] points) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slight simplification that avoids redundant iteration over the list and plays better with list fusion than zip
does.
let accumulate [] = do | |
poke (ptr `advancePtr` numPoints) nullPtr | |
go numPoints (PointArrayPtr (castPtr ptr)) | |
accumulate ((ix, point) : rest) = | |
withPoint point $ \(PointPtr pPtr) -> do | |
poke (ptr `advancePtr` ix) pPtr | |
accumulate rest | |
in accumulate (zip [0 ..] points) | |
let accumulate curPtr [] = do | |
poke curPtr nullPtr | |
go numPoints (PointArrayPtr (castPtr ptr)) | |
accumulate curPtr (point : rest) = | |
withPoint point $ \(PointPtr pPtr) -> do | |
poke curPtr pPtr | |
accumulate (curPtr `plusPtr` sizeReference) rest | |
in accumulate ptr points |
Nice comment about zipWithM_
not being a valid abstraction here! 👍
withScalarArray scalars go = do | ||
let numScalars = length scalars | ||
sizeReference = sizeOf (undefined :: Ptr ()) | ||
-- Allocate space for the scalars and a null terminator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is pretty surprising to see null termination. However, unlike usage of null termination for binary data or strings using, for an array of pointers I think it is fairly safe. What BLST designers should have done, is to produce some sort of error when number of pointers does not match where null termination is placed, since both carry the same information and there is a chance for a discrepancy.
It would be good to learn the expected semantics here. For example this would sound sensible, but I can't find any information on it:
- when there is a null pointer in a position that resulted in less points than was expected that it is safe, since one of those points could contain a null pointer
- when number of points (
numScalars
) supplied is reached and the next element in the array is not a null pointer than it is an error.
On a separate subject same comment as I made on withPointArray
applies here too
scalar <- scalarFromInteger s | ||
-- Here we filter out pairs that will not contribute to the result. | ||
-- This is also for safety, as the c_blst_to_affines C call | ||
-- will fail if the input contains the point at infinity. | ||
-- see https://github.com/supranational/blst/blob/165ec77634495175aefd045a48d3469af6950ea4/src/multi_scalar.c#L11C32-L11C37 | ||
-- We also filter out the zero scalar, as for any point pt | ||
-- we have: | ||
-- | ||
-- pt ^ 0 = id | ||
-- | ||
-- Which yields no contribution to summation, and | ||
-- thus we can skip the point and scalar pair. This filter | ||
-- saves us an extra input to the more expensive exponential | ||
-- operation. | ||
if blsIsInf pt || scalar == zeroScalar | ||
then return acc | ||
else return ((scalar, pt) : acc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend avoiding construction of scalar
if it is top be discarded:
scalar <- scalarFromInteger s | |
-- Here we filter out pairs that will not contribute to the result. | |
-- This is also for safety, as the c_blst_to_affines C call | |
-- will fail if the input contains the point at infinity. | |
-- see https://github.com/supranational/blst/blob/165ec77634495175aefd045a48d3469af6950ea4/src/multi_scalar.c#L11C32-L11C37 | |
-- We also filter out the zero scalar, as for any point pt | |
-- we have: | |
-- | |
-- pt ^ 0 = id | |
-- | |
-- Which yields no contribution to summation, and | |
-- thus we can skip the point and scalar pair. This filter | |
-- saves us an extra input to the more expensive exponential | |
-- operation. | |
if blsIsInf pt || scalar == zeroScalar | |
then return acc | |
else return ((scalar, pt) : acc) | |
-- Here we filter out pairs that will not contribute to the result. | |
-- This is also for safety, as the c_blst_to_affines C call | |
-- will fail if the input contains the point at infinity. | |
-- see https://github.com/supranational/blst/blob/165ec77634495175aefd045a48d3469af6950ea4/src/multi_scalar.c#L11C32-L11C37 | |
if blsIsInf pt | |
then pure acc | |
else do | |
scalar <- scalarFromInteger s | |
-- We also filter out the zero scalar, as for any point pt | |
-- we have: | |
-- | |
-- pt ^ 0 = id | |
-- | |
-- Which yields no contribution to summation, and | |
-- thus we can skip the point and scalar pair. This filter | |
-- saves us an extra input to the more expensive exponential | |
-- operation. | |
if scalar == zeroScalar | |
then return acc | |
else return ((scalar, pt) : acc) |
Also, in a one-to-one conversion we could avoid construction of zeroScalar
, by checking s == 0
instead, but I suspect because of modular arithmetic there could be other values of Integer
that would map to scalarFromInteger 0
. If I am wrong and there are no other values that map to 0 except 0, then I'd suggest this instead:
I'd recommend avoiding construction of scalar
if it is top be discarded:
scalar <- scalarFromInteger s | |
-- Here we filter out pairs that will not contribute to the result. | |
-- This is also for safety, as the c_blst_to_affines C call | |
-- will fail if the input contains the point at infinity. | |
-- see https://github.com/supranational/blst/blob/165ec77634495175aefd045a48d3469af6950ea4/src/multi_scalar.c#L11C32-L11C37 | |
-- We also filter out the zero scalar, as for any point pt | |
-- we have: | |
-- | |
-- pt ^ 0 = id | |
-- | |
-- Which yields no contribution to summation, and | |
-- thus we can skip the point and scalar pair. This filter | |
-- saves us an extra input to the more expensive exponential | |
-- operation. | |
if blsIsInf pt || scalar == zeroScalar | |
then return acc | |
else return ((scalar, pt) : acc) | |
-- Here we filter out pairs that will not contribute to the result. | |
-- This is also for safety, as the c_blst_to_affines C call | |
-- will fail if the input contains the point at infinity. | |
-- see https://github.com/supranational/blst/blob/165ec77634495175aefd045a48d3469af6950ea4/src/multi_scalar.c#L11C32-L11C37 | |
-- We also filter out the zero scalar, as for any point pt | |
-- we have: | |
-- | |
-- pt ^ 0 = id | |
-- | |
-- Which yields no contribution to summation, and | |
-- thus we can skip the point and scalar pair. This filter | |
-- saves us an extra input to the more expensive exponential | |
-- operation. | |
if blsIsInf pt || s == 0 | |
then pure acc | |
else do | |
scalar <- scalarFromInteger s | |
pure ((scalar, pt) : acc) |
-- by means of a modulo operation over the 'scalarPeriod'. | ||
-- Negative numbers will also be brought to the range | ||
-- [0, 'scalarPeriod' - 1] via modular reduction. | ||
blsMSM :: forall curve. BLS curve => Int -> [Integer] -> [Point curve] -> Point curve |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest having a list of tuples supplied, instead of two separate lists, because there are no surprises when these two lists have different length. (i.e. burden of correct zipping is put onto the caller of the function)
blsMSM :: forall curve. BLS curve => Int -> [Integer] -> [Point curve] -> Point curve | |
blsMSM :: forall curve. BLS curve => Int -> [(Integer, Point curve)] -> Point curve |
blsMSM threshold ss ps = unsafePerformIO $ do | ||
zeroScalar <- scalarFromInteger 0 | ||
filteredPoints <- | ||
foldM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foldM
is a left foldlM
, so this operation will reverse the order when compared to the input.
> foldM (\acc x -> (x:acc) <$ print x) [] [1,2,3,4]
1
2
3
4
[4,3,2,1]
My question is this indeed desired? If it isn't I'd suggest switching to foldrM
Considering that tests successfully check against the same naive implementation, I suspect that it all works out only because MSM operation is commutative. I am not sure if it is dangerous to rely on this property, but at the very least it surprising to see the order of input to be reversed.
, testProperty "MSM matches naive approach" $ \((ps, ss) :: ([BLS.Point curve], [BigInteger])) -> | ||
let pairs = [(p, i) | (BigInteger i, p) <- zip ss ps] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't want to generate two lists of different length just to immediately discard elements from the one that is longer.
, testProperty "MSM matches naive approach" $ \((ps, ss) :: ([BLS.Point curve], [BigInteger])) -> | |
let pairs = [(p, i) | (BigInteger i, p) <- zip ss ps] | |
, testProperty "MSM matches naive approach" $ \(pairs' :: [(BLS.Point curve, BigInteger)]) -> | |
let pairs = [(p, i) | (BigInteger i, p) <- pairs'] |
(zip ss ps) | ||
case filteredPoints of | ||
[] -> return blsZero | ||
-- If there is only one point, we refert to blsMult function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL there is a word in English refert
😄
(ps', ss') = unzip pairs | ||
in BLS.blsMSM 10 ss' ps' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will give an idea of how well both cases of blsMSM
function are tested.
(ps', ss') = unzip pairs | |
in BLS.blsMSM 10 ss' ps' | |
(ps', ss') = unzip pairs | |
threshold = 10 | |
in classify (length pairs <= threshold) "Below threshold" $ | |
BLS.blsMSM threshold ss' ps' |
@@ -165,15 +172,18 @@ import Data.ByteString (ByteString) | |||
import qualified Data.ByteString as BS | |||
import qualified Data.ByteString.Internal as BSI | |||
import qualified Data.ByteString.Unsafe as BSU | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant newline
Description
✨ PR: Add and Test Two MSM Implementations for BLS12-381
Summary
This PR introduces multi-scalar multiplication (MSM) for BLS12-381 elliptic curve points:
Changes
⚙️ Internal Module (
BLS12_381/Internal.hs
)blst_{p1s,p2s}_mult_pippenger
,blst_{p1s,p2s}_to_affine
, andc_blst_{p1s,p2s}_mult_pippenger_scratch_sizeof
withPointArray
,withScalarArray
,withAffineBlockArrayPtr
blsMSM
with filtering of points-at-infinity and zero scalars for safety and efficiency✅ Tests (
Test/EllipticCurve.hs
)blsMSM
against naive implementationsprop_randomFailsFinalVerify
to use group-based logic instead of point inequalityArbitrary
instance forPoint
blsZero
for better edge-case coverageChecklist
CHANGELOG.md
for the affected packages.New section is never added with the code changes. (See RELEASING.md)
.cabal
andCHANGELOG.md
files according to theversioning process.
.cabal
files for all affected packages are updated.If you change the bounds in a cabal file, that package itself must have a version increase. (See RELEASING.md)
.git-blame-ignore-revs