Skip to content
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

Introduce BLS digital signature #2223

Merged
merged 9 commits into from
Sep 13, 2022

Conversation

colibrishin
Copy link
Contributor

@colibrishin colibrishin commented Aug 18, 2022

This PR introduces a new digital signature, BLS, which has the aggregable private key, public key, and signature. Due to this property, it has an advantage in verifying multiple signatures with multiple public keys by reducing signatures with aggregation. The implementation uses Minimal-pubkey-size1 (G1 as Public key and G2 as Signature).

How fast it is?

Method 10 Signatures 100 Signatures
BLS Signature.Aggregate & FastAggregateVerify 6 ms 72.2 ms
BLS Signature.Aggregate & AggregateVerify 16.6 ms 127.3 ms
Secp256k1 Sign & Verify 81.4 ms 462.1 ms
  • Test signs and verifies the message.
  • Average in 10 runs.
  • Note that FastAggregateVerify2 is for the case where the same message but different signatures, and AggregateVerify3 is for the case where different messages and different signatures.
  • The BLS library has a first initialization time of 423ms. the above bls run-time is the subtracted time.

Classes

classDiagram
    class BlsPublicKey{
      byte[] KeyBytes
      Verify()
      VerifyProofOfPossession()
    }
    class BlsSignature{
      byte[] KeyBytes
      Aggregate()
      FastAggregateVerify()
      AggregateVerify()
      static MultiVerify()
    }
    class BlsPrivateKey{
      byte[] KeyBytes
      BlsSignature ProofOfPossession
      Sign()
    }
Loading
classDiagram
    PrivateKey <|-- IPrivateKey
    PublicKey <|-- IPublicKey
    BlsPrivateKey <|-- IPrivateKey
    BlsPublicKey <|-- IPublicKey
    DefaultCryptoBackend <|-- ICryptoBackend
    BlsCryptoBackend <|-- ICryptoBackend
    class BlsPrivateKey{
      -IReadonlyList<byte> KeyBytes => ByteArray
      -BlsPublicKey? publicKey
      +BlsPublicKey PublicKey
      +BlsSignature ProofOfPossession
      +ImmutableArray<byte> ByteArray
      +static PrivateKey FromString(string)
      +byte[] Sign(byte[])
      +ImmutableArray<byte> Sign(ImmutableArray<byte>)
      +static byte[] GenerateBytesFromHexString(string)
    }
    class PrivateKey{
      -IReadonlyList<byte> KeyBytes => ByteArray
      -PublicKey? publicKey
      +PublicKey PublicKey
      -ECPrivateKeyParameters KeyParam
      +ImmutableArray<byte> ByteArray
      +static PrivateKey FromString(string)
      +byte[] Sign(byte[])
      +ImmutableArray<byte> Sign(ImmutableArray<byte>)
      +byte[] Decrypt(byte[])
      +ImmutableArray<byte> Decrypt(ImmutableArray<byte>)
      +SymetricKey ExchangeKey(PublicKey)
      +static ECDomainParameters GetECParameters()
      +static ECDomainParameters GetECParameters(string)
      +static ECPrivateKeyParameters GenerateKeyParam()
      +static ECPrivateKeyParameters GeenrateKeyFromBytes(byte[])
      +static byte[] GenerateBytesFromHexString(string)
      -ECPoint CalculatePoint(ECPublicKeyParameters)
    }
    class BlsPublicKey{
      -IReadonlyList<byte> _publicKey
      +IReadonlyList<byte> KeyBytes => ToImmutableArray
      +ImmutableArray<byte> ByteArray
      +byte[] Verify(IReadonlyList<byte>, BlsSignature)
      +bool VerifyProofOfPossession(BlsSignature)
      +byte[] ToByteArray()
      +ImmutableArray<byte> ToImmutableArray()
    }
    class PublicKey{
      -IReadonly<byte> KeyBytes => ByteArray
      -ECPublicKeyParameters KeyParam
      -IPublicKey.KeyParam => KeyParam
      +static PrivateKey FromString(string)
      +byte[] Format(bool compress)
      +ImmutableArray<byte> ToImmutableArray(bool compress)
      +byte[] Verify(byte[])
      +ImmutableArray<byte> Verify(ImmutableArray<byte>)
      +byte[] Encrypt(byte[])
      +ImmutableArray<byte> Encrypt(ImmutableArray<byte>)
      +string ToString()
      -static ECDomainParameters GetECParameters(byte[] bs)
    }
    class BlsCryptoBackend {
      +BlsSignature Sign(HashDigest<T>, IPrivateKey)
      +bool Verify(HashDigetst<T>, byte[], IPublicKey)
      +bool AggregateVerify(BlsSignature, BlsPublicKey[])
      +bool FastAggregateVerify(BlsSignature, BlsPublicKey[])
      +byte[] GeneratePrivateKey()
      +BlsPublicKey GetPublicKey(BlsPrivateKey)
      +BlsSignature GetProofOfPossession(BlsPrivateKey)
      +bool VerifyPoP(BlsPublicKey, BlsSignature)
      +herumi.bls.SecretKey ValidateGetNativePrivateKey(BlsPrivateKey)
      +herumi.bls.PublicKey ValidateGetNativePublicKey(BlsPublicKey)
      +herumi.bls.Signature ValidateGetNativeSignature(BlsSignature)
      +herumi.bls.Msg ValidateGetNativeMsg(IReadonly<byte>)
      +Msg ConvertHashDigestToNativeMessage(HashDigest<T>)
    }
    class IPrivateKey{
      IReadonlyList<byte> KeyBytes
      IPublicKey PublicKey
      Sign(byte[])
    }
    class IPublicKey{
      IReadonlyList<byte> KeyBytes
      Verify(IReadOnlyList<byte> message, IReadOnlyList<byte> signature)
    }
    class ICryptoBackend{
      Sign(HashDigest<T>, IPrivateKey)
      Verify(HashDigetst<T>, byte[], IPublicKey)
    }
    class DefaultCryptoBackend{
      Sign(HashDigest<T>, IPrivateKey)
      Verify(HashDigetst<T>, byte[], IPublicKey)
    }
    class static CryptoConfig{
      -static ICryptoBackend<SHA256> _cryptoBackend
      -static BlsCryptoBackend<SHA256> _consensusCryptoBackend
      +static ICryptoBackend<SHA256> CryptoBackend
      +static BlsCryptoBackend<SHA256> ConsensusCryptoBackend
    }
Loading

Use cases

This BLS digital signature will be used in during the PBFT consensus for verifying multiple votes from nodes. There will be another key to store and manage.

sequenceDiagram
    ConsensusReactor->>ConsensusContext: Gets a propose message
    ConsensusContext->>+Context: Validates the propose
    Context->>+BlsPublicKey.Verify: Verifies the proposer's signature and its public key
    BlsPublicKey.Verify->>-Context: Returns the validity
    Context->>+ValidateNextBlock: Validates the proposed block
    ValidateNextBlock->>-Context: Returns the validity
    Context->>Context: Votes the block by result
    Context->>+Vote.Sign: Signs the vote
    Vote.Sign->>+BlsPrivateKey.Sign: Signs the vote
    BlsPrivateKey.Sign->>-Vote.Sign: Returns a signature
    Vote.Sign->>-Context: Returns a signature
    Context->>-ConsensusContext: Requests to send an vote message
    ConsensusContext->>ConsensusReactor: Sends the vote message
Loading

The validation of the block will be the aggregation of LastCommit. The same procedure can be done with the above (the verification of votes).

  • Proposer side
sequenceDiagram
    ConsensusReactor->>+ProposeBlock: Proposes a new block with the last commits
    loop until hits the last index of LastCommits
    ProposeBlock->>+BlsPublicKey.Verify: Validates a vote and its signature
    BlsPublicKey.Verify->>-ProposeBlock: Adds to lastCommit if the vote and signature of the vote are okay
    end
    ProposeBlock->>-ConsensusReactor: Throw if valid LastCommits are not +2/3 or return a Block
Loading
  • Non-Proposer side
sequenceDiagram
    ConsensusReactor->>ConsensusContext: Gets a propose message
    ConsensusContext->>+Context: Validates the propose
    Context->>+ValidateNextBlock: Validates the block
    ValidateNextBlock->>ValidateNextBlock: Gets the LastCommit from BlockMetadata
    loop until hits the last index of LastCommit
    ValidateNextBlock->>+BlsSignature.Aggregate: Aggregates the signatures
    BlsSignature.Aggregate->>-ValidateNextBlock: Returns an aggregated signature
    end
    ValidateNextBlock->>+BlsSignature.AggregateVerify: Validates the aggregated signature and public keys
    BlsSignature.AggregateVerify->>-ValidateNextBlock: Returns true if the signature and public keys are valid
    ValidateNextBlock->>-Context: Returns the validity
    Context->>-ConsensusContext: Votes the block by result
    ConsensusContext->>ConsensusReactor: Sends the vote message
Loading

In a bonding process of the validator, the public key should be validated with Proof of Possession4 created from its private key, for preventing Rouge Key Attack, which is simply saying, publishing the aggregated public key and not using the true public key of one's private key.

Further discussion

  • There is an ongoing discussion of forbidding zero value private key and public key (See Infinity pubkey and signature supranational/blst#11) due to the soundness and producing the infinite public key and signature. For now, This is allowed for the sake of standardization (IETF standard has no mention of blocking neither zero-value private key nor public key.)

  • The behavior of the Infinite Public key and Signature should be further investigated. For more details, See also Security of BLS batch verification.

  • The Proof of Possession is for validating whether the public key is not aggregated and proving there is a pair of private key. It should be enough checking once and that moment is when the non-validator peer is trying to bonding as a validator.

  • The byte size of Vote and LastCommit will be increased after the replacement of the Public Key and Signature to BLS.

  • This PR does not cover ProtectedPrivateKey, Web3KeyStore, Address and AddressExtensions modification.

The following discussion should be concerned and the thread is made at #2247.

Footnotes

  1. https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-2.1-2.2.1

  2. https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-fastaggregateverify

  3. https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-aggregateverify

  4. https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-proof-of-possession

@colibrishin colibrishin added the pbft Related to PBFT consensus label Aug 18, 2022
@colibrishin colibrishin self-assigned this Aug 18, 2022
@colibrishin colibrishin marked this pull request as draft August 18, 2022 05:21
@colibrishin colibrishin force-pushed the pbft-bls-implementation branch from 3827167 to 2c925b5 Compare August 23, 2022 07:45
@colibrishin colibrishin force-pushed the pbft-bls-implementation branch 10 times, most recently from f70bdb9 to 3f73480 Compare August 29, 2022 01:32
@colibrishin colibrishin marked this pull request as ready for review August 29, 2022 01:40
@colibrishin colibrishin force-pushed the pbft-bls-implementation branch 5 times, most recently from ccc25ed to 1a9fb7c Compare August 30, 2022 10:36
@longfin longfin removed the request for review from dahlia September 1, 2022 06:08
Comment on lines 52 to 70
public BlsSignature(IReadOnlyList<byte> signature)
{
if (signature is ImmutableArray<byte> i ? i.IsDefaultOrEmpty : !signature.Any())
{
throw new ArgumentNullException(
nameof(signature), "Signature is empty.");
}

if (signature.Count != KeyByteSize)
{
throw new ArgumentOutOfRangeException(
nameof(signature),
$"The key must be {KeyByteSize} bytes."
);
}

_signature = signature;
_ = CryptoConfig.ConsensusCryptoBackend.ValidateGetNativeSignature(ToByteArray());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There is some difference between IReadOnly and ImmutableArray / ImmutableList. Either this should accept immutable type or copy signature passed in as argument. Also I think _signature should also be an immutable type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed the type of _signature and other _privatekey and _publickey to ImmutableArray<byte>, and also IPublic/PrivateKey.KeyBytes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have set type IReadOnlyList for taking value as byte[] and let given value read-only in the constructor. The ImmutableList/Array can be also taken with IReadOnlyList.

The ImmutableList can be casted to ImmutableArray, and if the ImmutableList of null (i.e., default(ImmutableList)) comes in, then ArgumentNullException is thrown by ImmutableArray, which is matching with exception description.

Comment on lines 27 to 29
public static BlsSignature AggregateAll(this BlsSignature[] signatures) =>
signatures.Aggregate((x, y) => x.AggregateSignature(y));
Copy link
Contributor

Choose a reason for hiding this comment

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

This would depend on its usage, but there may be a better suited enumerable type instead of [].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

public static BlsSignature AggregateAll(this IEnumerable<BlsSignature> signatures) =>
signatures.Aggregate((x, y) => x.AggregateSignature(y));

How about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Depends on usage. Usually enforcing an immutable type is safer but with worse performance. I don't know enough about the exact context in which this will be used, but from the looks of it, this might get called on a "pool" of signatures that can change often. The IEnumerable signature signals that this method should be used synchronously or with some sort of locking mechanism from the caller.

Copy link
Contributor Author

@colibrishin colibrishin Sep 7, 2022

Choose a reason for hiding this comment

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

I think this can be used in asynchronous and synchronous in both situation. BlsSignature is immutable. The AggregateSignature internally creates new two native signatures from BLS library, and aggregates signatures, which are separate objects from given BlsSignatures, and the results is packed into new BlsSignature and returns it.

Does IEnumerable is signaling the method should be used synchronously mean that LINQ does not support asynchronous operation?

@colibrishin colibrishin force-pushed the pbft-bls-implementation branch 5 times, most recently from 41953cc to 883c431 Compare September 2, 2022 08:49
@colibrishin
Copy link
Contributor Author

colibrishin commented Sep 2, 2022

There is an ongoing discussion of forbidding zero value private key and public key (See supranational/blst#11) due to the soundness and producing the infinite public key and signature. For now, This is allowed for the sake of standardization (IETF standard has no mention of blocking neither zero-value private key nor public key.)

Eh, seems like newer IETF drafts such as 4 and 5 do not allow 0 SK. 🙄

A non-identity point is required because the identity public key has the property that the corresponding secret key is equal to zero, which means that the identity point is the unique valid signature for every message under this key. A malicious signer could take advantage of this fact to equivocate about which message he signed. While non-equivocation is not a required property for a signature scheme, equivocation is infeasible for BLS signatures under any nonzero secret key because it would require finding colliding inputs to the hash_to_point function, which is assumed to be collision resistant. Prohibiting SK == 0 eliminates the exceptional case, which may help to prevent equivocation-related security issues in protocols that use BLS signatures.1

Thanks for catching this out. I have added zero value check in BlsPrivateKey(IReadOnlyList<byte>) constructor. 883c431

Footnotes

  1. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#section-5.2

@greymistcube
Copy link
Contributor

Also, I'm not if it is exactly relevant, but there was this a while back. 🙄

@colibrishin colibrishin force-pushed the pbft-bls-implementation branch from 7080503 to 75589d0 Compare September 5, 2022 02:05
@colibrishin colibrishin force-pushed the pbft-bls-implementation branch 2 times, most recently from a8e0e8a to 0223675 Compare September 7, 2022 05:05
@colibrishin colibrishin force-pushed the pbft-bls-implementation branch 2 times, most recently from ef4bbc5 to c3dda59 Compare September 7, 2022 08:23
@colibrishin colibrishin force-pushed the pbft-bls-implementation branch from c3dda59 to 62eb854 Compare September 7, 2022 10:30
@pull-request-quantifier-deprecated

This PR has 1312 quantified lines of changes. In general, a change size of upto 200 lines is ideal for the best PR experience!


Quantification details

Label      : Extra Large
Size       : +1224 -88
Percentile : 100%

Total files changed: 20

Change summary by file extension:
.md : +24 -1
.cs : +1199 -87
.csproj : +1 -0

Change counts above are quantified counts, based on the PullRequestQuantifier customizations.

Why proper sizing of changes matters

Optimal pull request sizes drive a better predictable PR flow as they strike a
balance between between PR complexity and PR review overhead. PRs within the
optimal size (typical small, or medium sized PRs) mean:

  • Fast and predictable releases to production:
    • Optimal size changes are more likely to be reviewed faster with fewer
      iterations.
    • Similarity in low PR complexity drives similar review times.
  • Review quality is likely higher as complexity is lower:
    • Bugs are more likely to be detected.
    • Code inconsistencies are more likely to be detected.
  • Knowledge sharing is improved within the participants:
    • Small portions can be assimilated better.
  • Better engineering practices are exercised:
    • Solving big problems by dividing them in well contained, smaller problems.
    • Exercising separation of concerns within the code changes.

What can I do to optimize my changes

  • Use the PullRequestQuantifier to quantify your PR accurately
    • Create a context profile for your repo using the context generator
    • Exclude files that are not necessary to be reviewed or do not increase the review complexity. Example: Autogenerated code, docs, project IDE setting files, binaries, etc. Check out the Excluded section from your prquantifier.yaml context profile.
    • Understand your typical change complexity, drive towards the desired complexity by adjusting the label mapping in your prquantifier.yaml context profile.
    • Only use the labels that matter to you, see context specification to customize your prquantifier.yaml context profile.
  • Change your engineering behaviors
    • For PRs that fall outside of the desired spectrum, review the details and check if:
      • Your PR could be split in smaller, self-contained PRs instead
      • Your PR only solves one particular issue. (For example, don't refactor and code new features in the same PR).

How to interpret the change counts in git diff output

  • One line was added: +1 -0
  • One line was deleted: +0 -1
  • One line was modified: +1 -1 (git diff doesn't know about modified, it will
    interpret that line like one addition plus one deletion)
  • Change percentiles: Change characteristics (addition, deletion, modification)
    of this PR in relation to all other PRs within the repository.


Was this comment helpful? 👍  :ok_hand:  :thumbsdown: (Email)
Customize PullRequestQuantifier for this repository.

@colibrishin colibrishin merged commit 774cf86 into planetarium:pbft Sep 13, 2022
@colibrishin colibrishin mentioned this pull request Sep 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Extra Large pbft Related to PBFT consensus
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants