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

Implement Proof Of Possession capability for all public key crypto types #6010

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ members = [
"substrate/primitives/crypto/ec-utils",
"substrate/primitives/crypto/hashing",
"substrate/primitives/crypto/hashing/proc-macro",
"substrate/primitives/crypto/pubkeycrypto",
"substrate/primitives/crypto/pubkeycrypto/proc-macro",
"substrate/primitives/database",
"substrate/primitives/debug-derive",
"substrate/primitives/externalities",
Expand Down Expand Up @@ -1240,6 +1242,8 @@ sp-consensus-slots = { path = "substrate/primitives/consensus/slots", default-fe
sp-core = { path = "substrate/primitives/core", default-features = false }
sp-core-hashing = { default-features = false, path = "substrate/deprecated/hashing" }
sp-core-hashing-proc-macro = { default-features = false, path = "substrate/deprecated/hashing/proc-macro" }
sp-crypto-pubkeycrypto = {default-features = false, path = "substrate/primitives/crypto/pubkeycrypto"}
sp-crypto-pubkeycrypto-proc-macro = {default-features = false, path = "substrate/primitives/crypto/pubkeycrypto/proc-macro"}
sp-crypto-ec-utils = { default-features = false, path = "substrate/primitives/crypto/ec-utils" }
sp-crypto-hashing = { path = "substrate/primitives/crypto/hashing", default-features = false }
sp-crypto-hashing-proc-macro = { path = "substrate/primitives/crypto/hashing/proc-macro", default-features = false }
Expand Down Expand Up @@ -1349,7 +1353,7 @@ twox-hash = { version = "1.6.3", default-features = false }
unsigned-varint = { version = "0.7.2" }
url = { version = "2.4.0" }
void = { version = "1.0.2" }
w3f-bls = { version = "0.1.3", default-features = false }
w3f-bls = { version = "0.1.6", default-features = false }
wait-timeout = { version = "0.2" }
walkdir = { version = "2.5.0" }
wasm-bindgen-test = { version = "0.3.19" }
Expand Down
3 changes: 3 additions & 0 deletions substrate/primitives/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ sp-std = { workspace = true }
sp-debug-derive = { workspace = true }
sp-storage = { workspace = true }
sp-externalities = { optional = true, workspace = true }
sp-crypto-pubkeycrypto-proc-macro= { workspace = true }
futures = { optional = true, workspace = true }
dyn-clonable = { optional = true, workspace = true }
thiserror = { optional = true, workspace = true }
Expand Down Expand Up @@ -69,6 +70,8 @@ secp256k1 = { features = [

# bls crypto
w3f-bls = { optional = true, workspace = true }
sha2 = { workspace = true }

# bandersnatch crypto
bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = [
"substrate-curves",
Expand Down
15 changes: 13 additions & 2 deletions substrate/primitives/core/src/bandersnatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
use crate::crypto::VrfSecret;
use crate::crypto::{
ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair,
PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic,
ProofOfPossessionGenerator, ProofOfPossessionVerifier, PublicBytes, SecretStringError,
SignatureBytes, UncheckedFrom, VrfPublic,
};
use sp_crypto_pubkeycrypto_proc_macro::ProofOfPossession;

use bandersnatch_vrfs::{CanonicalSerialize, SecretKey};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
Expand Down Expand Up @@ -75,7 +77,7 @@ impl CryptoType for Signature {
type Seed = [u8; SEED_SERIALIZED_SIZE];

/// Bandersnatch secret key.
#[derive(Clone)]
#[derive(Clone, ProofOfPossession)]
pub struct Pair {
secret: SecretKey,
seed: Seed,
Expand Down Expand Up @@ -1087,4 +1089,13 @@ mod tests {

assert_eq!(enc1, enc2);
}

#[test]
fn good_proof_of_possession_should_work_bad_pop_should_fail() {
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let pop = pair.generate_proof_of_possession();
assert!(Pair::verify_proof_of_possession(pop.as_slice(), &pair.public()));
assert_eq!(Pair::verify_proof_of_possession(pop.as_slice(), &other_pair.public()), false);
}
}
104 changes: 100 additions & 4 deletions substrate/primitives/core/src/bls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@
//! curve.

use crate::crypto::{
CryptoType, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes, SecretStringError,
SignatureBytes, UncheckedFrom,
CryptoType, DeriveError, DeriveJunction, Pair as TraitPair, ProofOfPossessionGenerator,
ProofOfPossessionVerifier, PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom,
};

use alloc::vec::Vec;

use w3f_bls::{
DoublePublicKey, DoublePublicKeyScheme, DoubleSignature, EngineBLS, Keypair, Message,
SecretKey, SerializableToBytes, TinyBLS381,
BLSPoP, DoublePublicKey, DoublePublicKeyScheme, DoubleSignature, EngineBLS, Keypair, Message,
ProofOfPossession as BlsProofOfPossession,
ProofOfPossessionGenerator as BlsProofOfPossessionGenerator, SecretKey, SerializableToBytes,
TinyBLS381,
};

/// Required to generate PoP
use sha2::Sha256;

/// BLS-377 specialized types
pub mod bls377 {
pub use super::{PUBLIC_KEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE};
Expand Down Expand Up @@ -99,6 +104,10 @@ pub const PUBLIC_KEY_SERIALIZED_SIZE: usize =
pub const SIGNATURE_SERIALIZED_SIZE: usize =
<DoubleSignature<TinyBLS381> as SerializableToBytes>::SERIALIZED_BYTES_SIZE;

/// Signature serialized size
pub const POP_SERIALIZED_SIZE: usize =
<BLSPoP<TinyBLS381> as SerializableToBytes>::SERIALIZED_BYTES_SIZE;

/// A secret seed.
///
/// It's not called a "secret key" because ring doesn't expose the secret keys
Expand Down Expand Up @@ -227,6 +236,41 @@ impl<T: BlsBound> TraitPair for Pair<T> {
}
}

impl<T: BlsBound> ProofOfPossessionGenerator for Pair<T> {
fn generate_proof_of_possession(&mut self) -> Vec<u8> {
let r: [u8; POP_SERIALIZED_SIZE] =
<Keypair<T> as BlsProofOfPossessionGenerator<T, Sha256, _, BLSPoP<T>>>::generate_pok(
&mut self.0,
)
.to_bytes()
.try_into()
.expect("Signature serializer returns vectors of POP_SERIALIZED_SIZE size");
r.to_vec()
}
}

impl<T: BlsBound> ProofOfPossessionVerifier for Pair<T> {
fn verify_proof_of_possession(
proof_of_possession: &[u8],
allegedly_possessed_pubkey: &Self::Public,
) -> bool {
let proof_of_possession = BLSPoP::<T>::from_bytes(proof_of_possession);
let allegedly_possessed_pubkey_as_bls_pubkey =
match DoublePublicKey::<T>::from_bytes(&allegedly_possessed_pubkey.0) {
Ok(pk) => pk,
Err(_) => return false,
};

match proof_of_possession.ok() {
Some(proof_of_possession) => BlsProofOfPossession::<T, Sha256, _>::verify(
&proof_of_possession,
&allegedly_possessed_pubkey_as_bls_pubkey,
),
_ => false,
}
}
}

impl<T: BlsBound> CryptoType for Pair<T> {
type Pair = Pair<T>;
}
Expand Down Expand Up @@ -524,6 +568,7 @@ mod tests {
fn signature_serialization_works_for_bls381() {
signature_serialization_works::<bls381::BlsEngine>();
}

fn signature_serialization_doesnt_panic<E: BlsBound>() {
fn deserialize_signature<E: BlsBound>(
text: &str,
Expand All @@ -544,4 +589,55 @@ mod tests {
fn signature_serialization_doesnt_panic_for_bls381() {
signature_serialization_doesnt_panic::<bls381::BlsEngine>();
}

fn must_generate_proof_of_possession<E: BlsBound>() {
let mut pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
pair.generate_proof_of_possession();
}

#[test]
fn must_generate_proof_of_possession_for_bls377() {
must_generate_proof_of_possession::<bls377::BlsEngine>();
}

#[test]
fn must_generate_proof_of_possession_for_bls381() {
must_generate_proof_of_possession::<bls381::BlsEngine>();
}

fn good_proof_of_possession_must_verify<E: BlsBound>() {
let mut pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let pop = pair.generate_proof_of_possession();
assert!(Pair::<E>::verify_proof_of_possession(pop.as_slice(), &pair.public()));
}

#[test]
fn good_proof_of_possession_must_verify_for_bls377() {
good_proof_of_possession_must_verify::<bls377::BlsEngine>();
}

#[test]
fn good_proof_of_possession_must_verify_for_bls381() {
good_proof_of_possession_must_verify::<bls381::BlsEngine>();
}

fn proof_of_possession_must_fail_if_prover_does_not_possess_secret_key<E: BlsBound>() {
let mut pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::<E>::from_seed(b"23456789012345678901234567890123");
let pop = pair.generate_proof_of_possession();
assert_eq!(
Pair::<E>::verify_proof_of_possession(pop.as_slice(), &other_pair.public()),
false
);
}

#[test]
fn proof_of_possession_must_fail_if_prover_does_not_possess_secret_key_for_bls377() {
proof_of_possession_must_fail_if_prover_does_not_possess_secret_key::<bls377::BlsEngine>();
}

#[test]
fn proof_of_possession_must_fail_if_prover_does_not_possess_secret_key_for_bls381() {
proof_of_possession_must_fail_if_prover_does_not_possess_secret_key::<bls381::BlsEngine>();
}
}
54 changes: 54 additions & 0 deletions substrate/primitives/core/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,60 @@ pub trait Pair: CryptoType + Sized {
fn to_raw_vec(&self) -> Vec<u8>;
}

/// Pair which is able to generate proof of possession. This is implemented
/// in different trait to provide default behavoir
pub trait ProofOfPossessionGenerator: Pair
where
Self::Public: CryptoType,
{
/// The proof of possession generator is supposed to
/// to produce a "signature" with unique hash context that should
/// never be used in other signatures. This proves that
/// that the secret key is known to the prover. While prevent
/// malicious actors to trick an honest party to sign their
/// public key to mount a rogue key attack (See: Section 4.3 of
/// - Ristenpart, T., & Yilek, S. (2007). The power of proofs-of-possession: Securing multiparty
/// signatures against rogue-key attacks. In , Annual {{International Conference}} on the
/// {{Theory}} and {{Applications}} of {{Cryptographic Techniques} (pp. 228–245). : Springer.
fn generate_proof_of_possession(&mut self) -> Vec<u8> {
let pub_key_as_bytes = self.public().to_raw_vec();
let pop_context_tag: &[u8] = b"POP_";
let pop_statement = [pop_context_tag, pub_key_as_bytes.as_slice()].concat();
self.sign(pop_statement.as_slice()).to_raw_vec()
}
}

///The context which attached to pop message to attest its purpose
const POP_CONTEXT_TAG: &[u8; 4] = b"POP_";

/// Pair which is able to generate proof of possession. While you don't need a keypair
/// to verify a proof of possession (you only need a public key) we constrain on Pair
/// to use the Public and Signature types associated to Pair. This is implemented
/// in different trait (than Public Key) to provide default behavoir
pub trait ProofOfPossessionVerifier: Pair
where
Self::Public: CryptoType,
{
/// The proof of possession verifier is supposed to
/// to verify a signature with unique hash context that is
/// produced solely for this reason. This proves that
/// that the secret key is known to the prover.
fn verify_proof_of_possession(
proof_of_possesion: &[u8],
allegedly_possessesd_pubkey: &Self::Public,
) -> bool {
let pub_key_as_bytes = allegedly_possessesd_pubkey.to_raw_vec();
let pop_statement = [POP_CONTEXT_TAG, pub_key_as_bytes.as_slice()].concat();
let proof_of_possesion_as_signature: Option<Self::Signature> =
<Self::Signature as ByteArray>::from_slice(proof_of_possesion).ok();

match proof_of_possesion_as_signature {
Some(signature) => Self::verify(&signature, pop_statement, allegedly_possessesd_pubkey),
_ => false,
}
}
}

/// One type is wrapped by another.
pub trait IsWrappedBy<Outer>: From<Outer> + Into<Outer> {
/// Get a reference to the inner from the outer.
Expand Down
18 changes: 15 additions & 3 deletions substrate/primitives/core/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
//! Simple ECDSA secp256k1 API.

use crate::crypto::{
CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes,
SecretStringError, SignatureBytes,
CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair,
ProofOfPossessionGenerator, ProofOfPossessionVerifier, PublicBytes, SecretStringError,
SignatureBytes,
};

use sp_crypto_pubkeycrypto_proc_macro::ProofOfPossession;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -154,7 +157,7 @@ fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
}

/// A key pair.
#[derive(Clone)]
#[derive(Clone, ProofOfPossession)]
pub struct Pair {
public: Public,
secret: SecretKey,
Expand Down Expand Up @@ -629,4 +632,13 @@ mod test {
let key = sig.recover_prehashed(&msg).unwrap();
assert_ne!(pair.public(), key);
}

#[test]
fn good_proof_of_possession_should_work_bad_pop_should_fail() {
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let pop = pair.generate_proof_of_possession();
assert!(Pair::verify_proof_of_possession(pop.as_slice(), &pair.public()));
assert_eq!(Pair::verify_proof_of_possession(pop.as_slice(), &other_pair.public()), false);
}
}
Loading
Loading