From c5ee9ce429868ce8b278d65d3064acc5955a359d Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 9 Nov 2021 02:18:11 +0200 Subject: [PATCH] Change local challenge to use hash of the signature to make sure all bytes are taken into account --- crates/pallet-subspace/src/mock.rs | 7 ++- crates/sc-consensus-subspace/src/tests.rs | 6 +- .../sc-consensus-subspace/src/verification.rs | 10 +--- crates/sp-consensus-subspace/src/digests.rs | 8 +-- crates/subspace-core-primitives/src/lib.rs | 58 +++++++++++++++++-- crates/subspace-farmer/src/farming.rs | 12 ++-- crates/subspace-rpc-primitives/src/lib.rs | 6 +- crates/subspace-solving/src/lib.rs | 7 +-- 8 files changed, 78 insertions(+), 36 deletions(-) diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index ab215cfc02cdf..8aebfad000e23 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -35,7 +35,8 @@ use sp_runtime::{ Perbill, }; use subspace_core_primitives::{ - ArchivedBlockProgress, LastArchivedBlock, Piece, RootBlock, Sha256Hash, Signature, Tag, + ArchivedBlockProgress, LastArchivedBlock, LocalChallenge, Piece, RootBlock, Sha256Hash, + Signature, Tag, }; use subspace_solving::{SubspaceCodec, SOLUTION_SIGNING_CONTEXT}; @@ -205,7 +206,7 @@ pub fn go_to_block(keypair: &Keypair, block: u64, slot: u64) { piece_index: 0, encoding, signature: keypair.sign(ctx.bytes(&tag)).to_bytes().into(), - local_challenge: Signature::default(), + local_challenge: LocalChallenge::default(), tag, }, ); @@ -266,7 +267,7 @@ pub fn generate_equivocation_proof( piece_index, encoding, signature: signature.into(), - local_challenge: Signature::default(), + local_challenge: LocalChallenge::default(), tag, }, ); diff --git a/crates/sc-consensus-subspace/src/tests.rs b/crates/sc-consensus-subspace/src/tests.rs index cf9bb59a7c034..ad2aad974e446 100644 --- a/crates/sc-consensus-subspace/src/tests.rs +++ b/crates/sc-consensus-subspace/src/tests.rs @@ -43,7 +43,7 @@ use sp_runtime::{ }; use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider; use std::{cell::RefCell, task::Poll, time::Duration}; -use subspace_core_primitives::{Piece, Signature, Tag}; +use subspace_core_primitives::{LocalChallenge, Piece, Signature, Tag}; use subspace_solving::SubspaceCodec; use substrate_test_runtime::{Block as TestBlock, Hash}; @@ -726,7 +726,7 @@ pub fn dummy_claim_slot(slot: Slot, _epoch: &Epoch) -> Option<(PreDigest, Farmer piece_index: 0, encoding: Piece::default(), signature: Signature::default(), - local_challenge: Signature::default(), + local_challenge: LocalChallenge::default(), tag: Tag::default(), }, slot, @@ -799,7 +799,7 @@ fn propose_and_import_block( piece_index: 0, encoding, signature: signature.into(), - local_challenge: Signature::default(), + local_challenge: LocalChallenge::default(), tag, }, })], diff --git a/crates/sc-consensus-subspace/src/verification.rs b/crates/sc-consensus-subspace/src/verification.rs index 06f8a05303f61..6c5d26db44262 100644 --- a/crates/sc-consensus-subspace/src/verification.rs +++ b/crates/sc-consensus-subspace/src/verification.rs @@ -26,9 +26,7 @@ use sp_core::Public; use sp_runtime::{traits::DigestItemFor, traits::Header, RuntimeAppPublic}; use subspace_archiving::archiver; use subspace_core_primitives::{Randomness, Salt, Sha256Hash}; -use subspace_solving::{ - derive_global_challenge, is_local_challenge_valid, SubspaceCodec, TAG_SIZE, -}; +use subspace_solving::{derive_global_challenge, is_local_challenge_valid, SubspaceCodec}; /// Subspace verification parameters pub(super) struct VerificationParams<'a, B: 'a + BlockT> { @@ -196,11 +194,7 @@ fn check_piece( /// Returns true if `solution.tag` is within the solution range. fn is_within_solution_range(solution: &Solution, solution_range: u64) -> bool { - let target = u64::from_be_bytes( - solution.local_challenge[..TAG_SIZE] - .try_into() - .expect("Signature is always bigger than tag; qed"), - ); + let target = u64::from_be_bytes(solution.local_challenge.derive_target()); let (lower, is_lower_overflowed) = target.overflowing_sub(solution_range / 2); let (upper, is_upper_overflowed) = target.overflowing_add(solution_range / 2); diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index 9e6bdc1aa987d..8c96711c77696 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -24,7 +24,7 @@ use codec::{Codec, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_consensus_slots::Slot; use sp_runtime::{DigestItem, RuntimeDebug}; -use subspace_core_primitives::{Piece, Randomness, Signature, Tag}; +use subspace_core_primitives::{LocalChallenge, Piece, Randomness, Signature, Tag}; // TODO: better documentation here /// Solution @@ -38,8 +38,8 @@ pub struct Solution { pub encoding: Piece, /// Signature of the tag pub signature: Signature, - /// Local challenge derived with farmer's identity - pub local_challenge: Signature, + /// Local challenge derived from global challenge using farmer's identity. + pub local_challenge: LocalChallenge, /// Tag (hmac of encoding and salt) pub tag: Tag, } @@ -52,7 +52,7 @@ impl Solution { piece_index: 0u64, encoding: Piece::default(), signature: Signature::default(), - local_challenge: Signature::default(), + local_challenge: LocalChallenge::default(), tag: Tag::default(), } } diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index 643439d1e06ec..465ea82ababe8 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -47,13 +47,14 @@ pub type Sha256Hash = [u8; SHA256_HASH_SIZE]; /// Type of randomness. pub type Randomness = [u8; RANDOMNESS_LENGTH]; +/// Size of `Tag` in bytes. +pub const TAG_SIZE: usize = 8; + /// Type of the commitment for a particular piece. -/// -/// TODO: why not use `Commitment` directly? -pub type Tag = [u8; 8]; +pub type Tag = [u8; TAG_SIZE]; /// Salt used for creating commitment tags for pieces. -pub type Salt = [u8; 8]; +pub type Salt = [u8; TAG_SIZE]; const PUBLIC_KEY_LENGTH: usize = 32; @@ -131,6 +132,55 @@ impl AsRef<[u8]> for Signature { } } +/// A Ristretto Schnorr signature as bytes produced by `schnorrkel` crate. +#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct LocalChallenge( + #[cfg_attr(feature = "std", serde(with = "serde_arrays"))] [u8; SIGNATURE_LENGTH], +); + +impl Default for LocalChallenge { + fn default() -> Self { + Self([0u8; SIGNATURE_LENGTH]) + } +} + +impl From<[u8; SIGNATURE_LENGTH]> for LocalChallenge { + fn from(bytes: [u8; SIGNATURE_LENGTH]) -> Self { + Self(bytes) + } +} + +impl From for [u8; SIGNATURE_LENGTH] { + fn from(signature: LocalChallenge) -> Self { + signature.0 + } +} + +impl Deref for LocalChallenge { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for LocalChallenge { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl LocalChallenge { + /// Derive tags search target from local challenge. + pub fn derive_target(&self) -> Tag { + crypto::sha256_hash(&self.0)[..TAG_SIZE] + .try_into() + .expect("Signature is always bigger than tag; qed") + } +} + /// A piece of archival history in Subspace Network. /// /// Internally piece contains a record and corresponding witness that together with [`RootBlock`] of diff --git a/crates/subspace-farmer/src/farming.rs b/crates/subspace-farmer/src/farming.rs index d2cb047afb673..46c15bd2ccdda 100644 --- a/crates/subspace-farmer/src/farming.rs +++ b/crates/subspace-farmer/src/farming.rs @@ -6,9 +6,8 @@ use anyhow::Result; use futures::{future, future::Either}; use log::{debug, error, info, trace}; use std::time::Instant; -use subspace_core_primitives::{Salt, Signature}; +use subspace_core_primitives::{LocalChallenge, Salt}; use subspace_rpc_primitives::{SlotInfo, Solution, SolutionResponse}; -use subspace_solving::TAG_SIZE; /// Farming Instance to store the necessary information for the farming operations, /// and also a channel to stop/pause the background farming task @@ -105,9 +104,7 @@ async fn subscribe_to_slot_info( let maybe_solution = match commitments .find_by_range( - local_challenge[..TAG_SIZE] - .try_into() - .expect("Signature is always bigger than tag; qed"), + local_challenge.derive_target(), slot_info.solution_range, slot_info.salt, ) @@ -225,6 +222,9 @@ fn update_commitments( } /// Derive local challenge for farmer's identity from the global challenge. -fn derive_local_challenge>(global_challenge: C, identity: &Identity) -> Signature { +fn derive_local_challenge>( + global_challenge: C, + identity: &Identity, +) -> LocalChallenge { identity.sign(global_challenge.as_ref()).to_bytes().into() } diff --git a/crates/subspace-rpc-primitives/src/lib.rs b/crates/subspace-rpc-primitives/src/lib.rs index cca3c1a859bc2..9c9bd48c2cb16 100644 --- a/crates/subspace-rpc-primitives/src/lib.rs +++ b/crates/subspace-rpc-primitives/src/lib.rs @@ -18,7 +18,7 @@ use hex_buffer_serde::{Hex, HexForm}; use serde::{Deserialize, Serialize}; use subspace_core_primitives::objects::BlockObjectMapping; -use subspace_core_primitives::{Piece, PublicKey, Salt, Signature, Tag}; +use subspace_core_primitives::{LocalChallenge, Piece, PublicKey, Salt, Signature, Tag}; /// Type of a slot number. pub type SlotNumber = u64; @@ -104,8 +104,8 @@ pub struct Solution { pub encoding: Piece, /// Signature of the tag pub signature: Signature, - /// Local challenge derived with farmer's identity - pub local_challenge: Signature, + /// Local challenge derived from global challenge using farmer's identity. + pub local_challenge: LocalChallenge, /// Tag (hmac of encoding and salt) pub tag: Tag, } diff --git a/crates/subspace-solving/src/lib.rs b/crates/subspace-solving/src/lib.rs index de0b8023a7fa6..713d2d4e217a5 100644 --- a/crates/subspace-solving/src/lib.rs +++ b/crates/subspace-solving/src/lib.rs @@ -24,14 +24,11 @@ mod codec; pub use codec::SubspaceCodec; use schnorrkel::SignatureResult; use sha2::{Digest, Sha256}; -use subspace_core_primitives::{crypto, Piece, Randomness, Salt, Signature, Tag}; +use subspace_core_primitives::{crypto, LocalChallenge, Piece, Randomness, Salt, Tag, TAG_SIZE}; /// Signing context used for creating solution signatures by farmer pub const SOLUTION_SIGNING_CONTEXT: &[u8] = b"FARMER"; -/// Size of `Tag` in bytes. -pub const TAG_SIZE: usize = core::mem::size_of::(); - /// Check whether commitment tag of a piece is valid for a particular salt, which is used as a /// Proof-of-Replication pub fn is_tag_valid(piece: &Piece, salt: Salt, tag: Tag) -> bool { @@ -58,7 +55,7 @@ pub fn derive_global_challenge>(epoch_randomness: &Randomness, s /// Verify local challenge for farmer's public key that was derived from the global challenge. pub fn is_local_challenge_valid>( global_challenge: Tag, - local_challenge: &Signature, + local_challenge: &LocalChallenge, public_key: P, ) -> SignatureResult<()> { let signature = schnorrkel::Signature::from_bytes(local_challenge)?;