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

Paired-key Crypto Scheme #1705

Merged
merged 42 commits into from
Oct 15, 2023
Merged

Conversation

drskalman
Copy link
Contributor

BEEFY needs two cryptographic keys at the same time. Validators should sign BEEFY payload using both ECDSA and BLS key. The network will gossip a payload which contains a valid ECDSA key. The prover nodes aggregate the BLS keys if aggregation fails to verifies the validator which provided a valid ECDSA signature but an invalid BLS signature is subject to slashing.

As such BEEFY session should be initiated with both key. Currently there is no straight forward way of doing so, beside having a session with RuntimeApp corresponding to a crypto scheme contains both keys.

This pull request implement a generic paired_crypto scheme as well as implementing it for (ECDSA, BLS) pair.

@Lederstrumpf Lederstrumpf added the T0-node This PR/Issue is related to the topic “node”. label Sep 26, 2023
@drskalman
Copy link
Contributor Author

I could change the sign algorithm to sign the second signature using the first key. This potentially might give us a 7x speed up on verification time during validators gossip of BEEFY messages:

test double::tests::test_bls_verify_signatures_chaum_pedersen  ... bench:   2,967,600 ns/iter (+/- 15,817)                                                                                                                                                    
test double::tests::test_bls_verify_signatures_simple_bls      ... bench:   4,304,517 ns/iter (+/- 6,141,991)                                                                                                                                                 
test double::tests::test_verify_EdDSA_signatures               ... bench:     352,084 ns/iter (+/- 1,802)                                                                                                                                                     
test double::tests::test_verify_Schnorr_on_secp256k1_signatures ... bench:     394,758 ns/iter (+/- 2,064)                                                                                                                                                     

I couldn't find an readily implemented ECDSA for Arkworks so I used Schnorr on Secp256k1 instead but I don't expect a huge difference between ECDSA and a single schnorr verification. The result is on par with this experiment showin BLS verification being 10 times slower than ECDSA.

Copy link
Member

@davxy davxy left a comment

Choose a reason for hiding this comment

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

First pass.

In the meantime I'm going to read the interesting article you shared :-)

substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
substrate/primitives/core/src/paired_crypto.rs Outdated Show resolved Hide resolved
drskalman and others added 6 commits October 5, 2023 14:44
…and SCALE.

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Davide Galassi <davxy@datawok.net>
@drskalman
Copy link
Contributor Author

WDYT?

I think currently it gets the public key from the vote messages. Nonetheless, I could imagine that someone would take advantage of the optimization.

It is less relevant with the signatures, but we also for example do other stuff with the signatures, like aggregating them which in BLS, we need them to be in the form of points on the curve rather than the serialized form. But that is much less common than the public key reuse scenario.

So I'll try to make it infallible with constraining first.

@drskalman
Copy link
Contributor Author

Doing this:

impl<LeftPublic: PublicT, RightPublic: PublicT, const LEFT_PLUS_RIGHT_LEN: usize, >                                                                           
        UncheckedFrom<[u8; LEFT_PLUS_RIGHT_LEN]> for Public<LeftPublic, RightPublic, LEFT_PLUS_RIGHT_LEN> where LeftPublic : UncheckedFrom<[u8; LeftPublic::LEN]>

Results in:

error: generic parameters may not be used in const operations                                                                                                 
   --> substrate/primitives/core/src/paired_crypto.rs:209:138                                                                                                 
    |                                                                                                                                                         
209 | ...e LeftPublic : UncheckedFrom<[u8; LeftPublic::LEN]>                                                                                                  
    |                                      ^^^^^^^^^^^^^^^ cannot perform const operation using `LeftPublic`                                                  
    |                                                                                                                                                         
    = note: type parameters may not be used in const expressions                                                                                              

I'm can not introduce a new const generic only for the implementation of UncheckedFrom because Rust complains that Public does not depends on:

impl<LeftPublic: PublicT, RightPublic: PublicT, const LEFT_PLUS_RIGHT_LEN: usize, const LEFT_LEN: usize>                                                      
        UncheckedFrom<[u8; LEFT_PLUS_RIGHT_LEN]> for Public<LeftPublic, RightPublic, LEFT_PLUS_RIGHT_LEN> where LeftPublic : UncheckedFrom<[u8; LEFT_LEN]>    

Results in:

error[E0207]: the const parameter `LEFT_LEN` is not constrained by the impl trait, self type, or predicates                                                   
   --> substrate/primitives/core/src/paired_crypto.rs:208:83                                                                                                  
    |                                                                                                                                                         
208 | impl<LeftPublic: PublicT, RightPublic: PublicT, const LEFT_PLUS_RIGHT_LEN: usize, const LEFT_LEN: usize>                                                
    |                                                                                   ^^^^^^^^^^^^^^^^^^^^^ unconstrained const parameter                   
    |                                                                                                                                                         
    = note: expressions using a const parameter must map each value to a distinct output value                                                                
    = note: proving the result of expressions other than the parameter are unique is not supported                                                            

This also doesn't work (not even in nightly):

struct foo<const L: usize, const R: usize, > 
{
 inner : [u8; L + R]
}

as in

error: generic parameters may not be used in const operations
 --> src/main.rs:3:15
  |
3 |  inner : [u8; L + R]
  |               ^ cannot perform const operation using `L`
  |
  = help: const parameters may only be used as standalone arguments, i.e. `L`

This compiles only in nightly:

#![feature(generic_const_exprs)]
struct foo<const L: usize, const R: usize, const L_PLUS_R: usize> 
{
    inner : [u8; L_PLUS_R],
}

trait bar {
    
}

impl<const L: usize, const R: usize,> bar for foo<L, R, {L+R}>
{
}

with scary warnings about horrible consequences of using generic_const_exprs which we do not want to get involved with. Additionally it adds two more generic consts all over the code, making the code much less readable.

Note that in both of your proposed solutions we need to deal with this, because we need to finally convert Public::Inner into LeftPublic and RightPublic and (their Signature counterparts) either at inception of the object (as it is now) or when we want to sign or verify a signature. While we could return false in verification, I assume signing need to be infallible as well.

So checking Self::LEN = Left::LEN + Right::LEN during runtime and in the tests, seems to be the only option for now till the compiler matures more to handle const expressions better.

- implemented `test_length_of_paired_ecdsa_and_bls377_public_key_and_signature_is_correct`
@drskalman
Copy link
Contributor Author

@davxy feel free to remove the debug_assert!s if you don't like them.

@drskalman
Copy link
Contributor Author

@davxy I removed the internal object now it is just an array like other cryptos.

Copy link
Member

@davxy davxy left a comment

Choose a reason for hiding this comment

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

Looks good 🟢

@davxy davxy requested a review from a team October 11, 2023 11:06
Comment on lines +390 to +405
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
seed: Option<Self::Seed>,
) -> Result<(Self, Option<Self::Seed>), DeriveError> {
let path: Vec<_> = path.collect();

let left = self.left.derive(path.iter().cloned(), seed.map(|s| s.into()))?;
let right = self.right.derive(path.into_iter(), seed.map(|s| s.into()))?;

let seed = match (left.1, right.1) {
(Some(l), Some(r)) if l.as_ref() == r.as_ref() => Some(l.into()),
_ => None,
};

Ok((Self { left: left.0, right: right.0 }, seed))
Copy link
Member

Choose a reason for hiding this comment

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

Mmm... here if the two crypto schemes have different ways to derive the seed we end up with a None seed.
I don't like this a lot. But if is good for the current application I'm not going to block things for this.

Would be nice to at least write this thing explicitly over this method docs.

Copy link
Contributor

Choose a reason for hiding this comment

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

@davxy something like #1705 (comment)?
can also integrate directly into PairT::derive documentation, although it's specific to the implementation here.

Copy link
Contributor Author

@drskalman drskalman Oct 11, 2023

Choose a reason for hiding this comment

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

The only way to be able to return a seed is to make the paired_crypto seed 64 bytes which is not inline with all other crypto schemes (and make it not composable i.e. you can't have a pair of paired schemes).

There is a comment in crypto.rs which says in some crypto schemes it is not possible to provide a direct seed after derivation:

https://github.com/w3f/polkadot-sdk//blob/skalman-paired-crypto-scheme/substrate/primitives/core/src/crypto.rs#L930

We can also add paired_crypto scheme to that comment perhaps.

Copy link
Contributor

@Lederstrumpf Lederstrumpf left a comment

Choose a reason for hiding this comment

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

tACK - and thanks for removing the seed duplication (a01a814).

I recognize this change muddies the derive return though (#1705 (comment)).

@acatangiu
Copy link
Contributor

@davxy is this good to merge?

@davxy
Copy link
Member

davxy commented Oct 13, 2023

@davxy is this good to merge?

Yes

@davxy davxy merged commit 1b34571 into paritytech:master Oct 15, 2023
109 of 111 checks passed
ordian added a commit that referenced this pull request Oct 16, 2023
* master: (54 commits)
  Publish `xcm-emulator` crate (#1881)
  Adding migrations to clean Rococo Gov 1 storage & reserved funds (#1849)
  Arkworks Elliptic Curve utils overhaul (#1870)
  Fix typos (#1878)
  fix: GoAhead signal only set when runtime upgrade is enacted from parachain side (#1176)
  Refactor staking ledger (#1484)
  Paired-key Crypto Scheme (#1705)
  Include polkadot version in artifact path (#1828)
  add link to rfc-0001 in broker README (#1862)
  Discard `Executor` (#1855)
  Macros to use path instead of ident (#1474)
  Remove clippy clone-double-ref lint noise (#1860)
  Refactor alliance benchmarks to v2 (#1868)
  Check executor params coherence (#1774)
  frame: use derive-impl for beefy and mmr pallets (#1867)
  sc-consensus-beefy: improve gossip logic (#1852)
  Adds instance support for composite enums (#1857)
  Fix links to implementers' guide (#1865)
  Disabled validators runtime API (#1257)
  Adding `try_state` hook for `Treasury` pallet (#1820)
  ...
ordian added a commit that referenced this pull request Oct 16, 2023
…ribution

* tsv-disabling-backing: (54 commits)
  Publish `xcm-emulator` crate (#1881)
  Adding migrations to clean Rococo Gov 1 storage & reserved funds (#1849)
  Arkworks Elliptic Curve utils overhaul (#1870)
  Fix typos (#1878)
  fix: GoAhead signal only set when runtime upgrade is enacted from parachain side (#1176)
  Refactor staking ledger (#1484)
  Paired-key Crypto Scheme (#1705)
  Include polkadot version in artifact path (#1828)
  add link to rfc-0001 in broker README (#1862)
  Discard `Executor` (#1855)
  Macros to use path instead of ident (#1474)
  Remove clippy clone-double-ref lint noise (#1860)
  Refactor alliance benchmarks to v2 (#1868)
  Check executor params coherence (#1774)
  frame: use derive-impl for beefy and mmr pallets (#1867)
  sc-consensus-beefy: improve gossip logic (#1852)
  Adds instance support for composite enums (#1857)
  Fix links to implementers' guide (#1865)
  Disabled validators runtime API (#1257)
  Adding `try_state` hook for `Treasury` pallet (#1820)
  ...
@Polkadot-Forum
Copy link

This pull request has been mentioned on Polkadot Forum. There might be relevant details there:

https://forum.polkadot.network/t/polkadot-kusama-bridge/2971/6

Lederstrumpf added a commit that referenced this pull request Oct 24, 2023
…1815)

Next step in process of making BEEFY being able to generate both ECDSA
and BLS signature after #1705. It allows BEEFY to use a pair of ECDSA
and BLS key as a AuthorityId.

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Robert Hambrock <roberthambrock@gmail.com>
s0me0ne-unkn0wn pushed a commit that referenced this pull request Oct 29, 2023
…1815)

Next step in process of making BEEFY being able to generate both ECDSA
and BLS signature after #1705. It allows BEEFY to use a pair of ECDSA
and BLS key as a AuthorityId.

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Robert Hambrock <roberthambrock@gmail.com>
@Polkadot-Forum
Copy link

This pull request has been mentioned on Polkadot Forum. There might be relevant details there:

https://forum.polkadot.network/t/polkadot-kusama-bridge/2971/7

bgallois pushed a commit to duniter/duniter-polkadot-sdk that referenced this pull request Mar 25, 2024
BEEFY needs two cryptographic keys at the same time. Validators should
sign BEEFY payload using both ECDSA and BLS key. The network will gossip
a payload which contains a valid ECDSA key. The prover nodes aggregate
the BLS keys if aggregation fails to verifies the validator which
provided a valid ECDSA signature but an invalid BLS signature is subject
to slashing.

As such BEEFY session should be initiated with both key. Currently there
is no straight forward way of doing so, beside having a session with
RuntimeApp corresponding to a crypto scheme contains both keys.

This pull request implement a generic paired_crypto scheme as well as
implementing it for (ECDSA, BLS) pair.

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Robert Hambrock <roberthambrock@gmail.com>
bgallois pushed a commit to duniter/duniter-polkadot-sdk that referenced this pull request Mar 25, 2024
…aritytech#1815)

Next step in process of making BEEFY being able to generate both ECDSA
and BLS signature after paritytech#1705. It allows BEEFY to use a pair of ECDSA
and BLS key as a AuthorityId.

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Robert Hambrock <roberthambrock@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T0-node This PR/Issue is related to the topic “node”.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants