Skip to content
This repository has been archived by the owner on Feb 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request paritytech#511 from subspace/fix-canonical-signatures
Browse files Browse the repository at this point in the history
Replace non-canonical signatures with canonical for local challenge and PoR
  • Loading branch information
nazar-pc authored May 29, 2022
2 parents eddcaa2 + 03977f4 commit 640f1c8
Show file tree
Hide file tree
Showing 27 changed files with 403 additions and 300 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

32 changes: 13 additions & 19 deletions crates/pallet-subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ use sp_consensus_subspace::offence::{OffenceDetails, OffenceError, OnOffenceHand
use sp_consensus_subspace::verification::{
PieceCheckParams, VerificationError, VerifySolutionParams,
};
use sp_consensus_subspace::{verification, EquivocationProof, FarmerPublicKey, SignedVote, Vote};
use sp_io::hashing;
use sp_consensus_subspace::{
derive_randomness, verification, EquivocationProof, FarmerPublicKey, SignedVote, Vote,
};
use sp_runtime::generic::DigestItem;
use sp_runtime::traits::{
BlockNumberProvider, Hash, Header as HeaderT, One, SaturatedConversion, Saturating, Zero,
Expand All @@ -59,12 +60,10 @@ use sp_runtime::DispatchError;
use sp_std::collections::btree_map::BTreeMap;
use sp_std::prelude::*;
use subspace_core_primitives::{
crypto, Randomness, RootBlock, Salt, Signature, PIECE_SIZE, RANDOMNESS_LENGTH, SALT_SIZE,
crypto, Randomness, RootBlock, Salt, PIECE_SIZE, RANDOMNESS_LENGTH, SALT_SIZE,
};
use subspace_solving::{REWARD_SIGNING_CONTEXT, SOLUTION_SIGNING_CONTEXT};
use subspace_solving::REWARD_SIGNING_CONTEXT;

const GLOBAL_CHALLENGE_HASHING_PREFIX: &[u8] = b"global_challenge";
const GLOBAL_CHALLENGE_HASHING_PREFIX_LEN: usize = GLOBAL_CHALLENGE_HASHING_PREFIX.len();
const SALT_HASHING_PREFIX: &[u8] = b"salt";
const SALT_HASHING_PREFIX_LEN: usize = SALT_HASHING_PREFIX.len();

Expand Down Expand Up @@ -789,7 +788,7 @@ impl<T: Config> Pallet<T> {
CurrentSlot::<T>::put(pre_digest.slot);

{
let key = (pre_digest.solution.public_key, pre_digest.slot);
let key = (pre_digest.solution.public_key.clone(), pre_digest.slot);
if ParentBlockVoters::<T>::get().contains_key(&key) {
let (public_key, slot) = key;

Expand Down Expand Up @@ -857,16 +856,13 @@ impl<T: Config> Pallet<T> {
}

// Extract PoR randomness from pre-digest.
let por_randomness: Randomness = hashing::blake2_256(&{
let mut input =
[0u8; GLOBAL_CHALLENGE_HASHING_PREFIX_LEN + mem::size_of::<Signature>()];
input[..GLOBAL_CHALLENGE_HASHING_PREFIX_LEN]
.copy_from_slice(GLOBAL_CHALLENGE_HASHING_PREFIX);
input[GLOBAL_CHALLENGE_HASHING_PREFIX_LEN..]
.copy_from_slice(&pre_digest.solution.signature);

input
});
// Tag signature is validated by the client and is always valid here.
let por_randomness: Randomness = derive_randomness(
&pre_digest.solution.public_key,
pre_digest.solution.tag,
&pre_digest.solution.tag_signature,
)
.expect("Tag signature is verified by the client and is always valid; qed");
// Store PoR randomness for block duration as it might be useful.
PorRandomness::<T>::put(por_randomness);

Expand Down Expand Up @@ -1414,7 +1410,6 @@ fn check_vote<T: Config>(
max_plot_size: vote_verification_data.max_plot_size,
total_pieces: vote_verification_data.total_pieces,
}),
solution_signing_context: &schnorrkel::signing_context(SOLUTION_SIGNING_CONTEXT),
},
) {
debug!(
Expand Down Expand Up @@ -1571,7 +1566,6 @@ impl<T: Config> subspace_runtime_primitives::FindVotingRewardAddresses<T::Accoun
impl<T: Config> frame_support::traits::Randomness<T::Hash, T::BlockNumber> for Pallet<T> {
fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) {
let mut subject = subject.to_vec();
subject.reserve(RANDOMNESS_LENGTH);
subject.extend_from_slice(
PorRandomness::<T>::get()
.expect("PoR randomness is always set in block initialization; qed")
Expand Down
41 changes: 19 additions & 22 deletions crates/pallet-subspace/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ use subspace_core_primitives::{
ArchivedBlockProgress, LastArchivedBlock, LocalChallenge, Piece, Randomness, RootBlock, Salt,
Sha256Hash, Solution, Tag, PIECE_SIZE,
};
use subspace_solving::{SubspaceCodec, REWARD_SIGNING_CONTEXT, SOLUTION_SIGNING_CONTEXT};
use subspace_solving::{
create_tag, create_tag_signature, derive_global_challenge, derive_local_challenge,
SubspaceCodec, REWARD_SIGNING_CONTEXT,
};

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
Expand Down Expand Up @@ -190,11 +193,10 @@ pub fn go_to_block(
};

let subspace_codec = SubspaceCodec::new(keypair.public.as_ref());
let ctx = schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT);
let piece_index = 0;
let mut encoding = Piece::default();
subspace_codec.encode(&mut encoding, piece_index).unwrap();
let tag: Tag = subspace_solving::create_tag(&encoding, {
let tag: Tag = create_tag(&encoding, {
let salts = Subspace::salts();
if salts.switch_next_block {
salts.next.unwrap()
Expand All @@ -210,8 +212,11 @@ pub fn go_to_block(
reward_address,
piece_index: 0,
encoding,
signature: keypair.sign(ctx.bytes(&tag)).to_bytes().into(),
local_challenge: LocalChallenge::default(),
tag_signature: create_tag_signature(keypair, tag),
local_challenge: LocalChallenge {
output: [0; 32],
proof: [0; 64],
},
tag,
},
);
Expand Down Expand Up @@ -267,12 +272,10 @@ pub fn generate_equivocation_proof(
let current_block = System::block_number();
let current_slot = CurrentSlot::<Test>::get();

let ctx = schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT);
let encoding = Piece::default();
let tag: Tag = [(current_block % 8) as u8; 8];

let public_key = FarmerPublicKey::unchecked_from(keypair.public.to_bytes());
let signature = keypair.sign(ctx.bytes(&tag)).to_bytes();

let make_header = |piece_index, reward_address: <Test as frame_system::Config>::AccountId| {
let parent_hash = System::parent_hash();
Expand All @@ -283,8 +286,11 @@ pub fn generate_equivocation_proof(
reward_address,
piece_index,
encoding: encoding.clone(),
signature: signature.into(),
local_challenge: LocalChallenge::default(),
tag_signature: create_tag_signature(keypair, tag),
local_challenge: LocalChallenge {
output: [0; 32],
proof: [0; 64],
},
tag,
},
);
Expand Down Expand Up @@ -381,17 +387,11 @@ pub fn create_signed_vote(
encoding: Piece,
reward_address: <Test as frame_system::Config>::AccountId,
) -> SignedVote<u64, <Block as BlockT>::Hash, <Test as frame_system::Config>::AccountId> {
let solution_signing_context = schnorrkel::signing_context(SOLUTION_SIGNING_CONTEXT);
let reward_signing_context = schnorrkel::signing_context(REWARD_SIGNING_CONTEXT);

let global_challenge =
subspace_solving::derive_global_challenge(global_randomnesses, slot.into());
let local_challenge = keypair
.sign(solution_signing_context.bytes(&global_challenge))
.to_bytes()
.into();
let global_challenge = derive_global_challenge(global_randomnesses, slot.into());

let tag = subspace_solving::create_tag(&encoding, salt);
let tag = create_tag(&encoding, salt);

let vote = Vote::<u64, <Block as BlockT>::Hash, _>::V0 {
height,
Expand All @@ -402,11 +402,8 @@ pub fn create_signed_vote(
reward_address,
piece_index: 0,
encoding,
signature: keypair
.sign(solution_signing_context.bytes(&tag))
.to_bytes()
.into(),
local_challenge,
tag_signature: create_tag_signature(keypair, tag),
local_challenge: derive_local_challenge(keypair, global_challenge),
tag,
},
};
Expand Down
2 changes: 1 addition & 1 deletion crates/pallet-subspace/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ fn vote_invalid_solution_signature() {
);

let Vote::V0 { solution, .. } = &mut signed_vote.vote;
solution.signature = rand::random::<[u8; 64]>().into();
solution.tag_signature.output = rand::random();

// Fix signed vote signature after changed contents
signed_vote.signature = FarmerSignature::unchecked_from(
Expand Down
35 changes: 9 additions & 26 deletions crates/sc-consensus-subspace-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use std::time::Duration;
use subspace_archiving::archiver::ArchivedSegment;
use subspace_core_primitives::Solution;
use subspace_rpc_primitives::{
FarmerMetadata, RewardSignature, RewardSigningInfo, SlotInfo, SolutionResponse,
FarmerMetadata, RewardSignatureResponse, RewardSigningInfo, SlotInfo, SolutionResponse,
};

const SOLUTION_TIMEOUT: Duration = Duration::from_secs(2);
Expand Down Expand Up @@ -80,7 +80,7 @@ pub trait SubspaceRpcApi {
fn subscribe_reward_signing(&self);

#[method(name = "subspace_submitRewardSignature")]
fn submit_reward_signature(&self, reward_signature: RewardSignature) -> RpcResult<()>;
fn submit_reward_signature(&self, reward_signature: RewardSignatureResponse) -> RpcResult<()>;

/// Archived segment subscription
#[subscription(
Expand All @@ -103,7 +103,7 @@ struct SolutionResponseSenders {
#[derive(Default)]
struct BlockSignatureSenders {
current_hash: H256,
senders: Vec<async_oneshot::Sender<RewardSignature>>,
senders: Vec<async_oneshot::Sender<RewardSignatureResponse>>,
}

#[derive(Default)]
Expand Down Expand Up @@ -248,35 +248,18 @@ where
let forward_solution_fut = async move {
if let Ok(solution_response) = response_receiver.await {
if let Some(solution) = solution_response.maybe_solution {
let public_key =
match FarmerPublicKey::from_slice(&solution.public_key) {
Ok(public_key) => public_key,
Err(()) => {
warn!(
"Failed to convert public key: {:?}",
solution.public_key
);
return;
}
};
let public_key = FarmerPublicKey::from_slice(&solution.public_key)
.expect("Always correct length; qed");
let reward_address =
match FarmerPublicKey::from_slice(&solution.reward_address) {
Ok(public_key) => public_key,
Err(()) => {
warn!(
"Failed to convert reward address: {:?}",
solution.reward_address,
);
return;
}
};
FarmerPublicKey::from_slice(&solution.reward_address)
.expect("Always correct length; qed");

let solution = Solution {
public_key,
reward_address,
piece_index: solution.piece_index,
encoding: solution.encoding,
signature: solution.signature,
tag_signature: solution.tag_signature,
local_challenge: solution.local_challenge,
tag: solution.tag,
};
Expand Down Expand Up @@ -403,7 +386,7 @@ where
);
}

fn submit_reward_signature(&self, reward_signature: RewardSignature) -> RpcResult<()> {
fn submit_reward_signature(&self, reward_signature: RewardSignatureResponse) -> RpcResult<()> {
let reward_signature_senders = self.reward_signature_senders.clone();

// TODO: This doesn't track what client sent a solution, allowing some clients to send
Expand Down
7 changes: 6 additions & 1 deletion crates/sc-consensus-subspace/src/aux_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ use codec::{Decode, Encode};

use sc_client_api::backend::AuxStore;
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sp_consensus_subspace::SubspaceBlockWeight;

/// The cumulative weight of a Subspace block, i.e. sum of block weights starting
/// at this block until the genesis block.
///
/// The closer solution's tag is to the target, the heavier it is.
type SubspaceBlockWeight = u128;

/// The aux storage key used to store the block weight of the given block hash.
fn block_weight_key<H: Encode>(block_hash: H) -> Vec<u8> {
Expand Down
31 changes: 25 additions & 6 deletions crates/sc-consensus-subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use sc_consensus_slots::{
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE};
use sc_utils::mpsc::TracingUnboundedSender;
use schnorrkel::context::SigningContext;
use schnorrkel::PublicKey;
use sp_api::{ApiError, ApiExt, BlockT, HeaderT, NumberFor, ProvideRuntimeApi, TransactionFor};
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult};
Expand Down Expand Up @@ -80,7 +81,7 @@ use std::pin::Pin;
use std::sync::Arc;
use subspace_archiving::archiver::ArchivedSegment;
use subspace_core_primitives::{BlockNumber, RootBlock, Salt, Sha256Hash, Solution};
use subspace_solving::{REWARD_SIGNING_CONTEXT, SOLUTION_SIGNING_CONTEXT};
use subspace_solving::{derive_global_challenge, derive_target, REWARD_SIGNING_CONTEXT};

/// Information about new slot that just arrived
#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -415,7 +416,6 @@ where
force_authoring,
backoff_authoring_blocks,
subspace_link: subspace_link.clone(),
solution_signing_context: schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT),
reward_signing_context: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
block_proposal_slot_portion,
max_block_proposal_slot_portion,
Expand Down Expand Up @@ -618,7 +618,6 @@ pub struct SubspaceVerifier<Block: BlockT, Client, SelectChain, SN> {
select_chain: SelectChain,
slot_now: SN,
telemetry: Option<TelemetryHandle>,
solution_signing_context: SigningContext,
reward_signing_context: SigningContext,
block: PhantomData<Block>,
}
Expand Down Expand Up @@ -776,7 +775,6 @@ where
solution_range,
salt,
piece_check_params: None,
solution_signing_context: &self.solution_signing_context,
},
reward_signing_context: &self.reward_signing_context,
},
Expand Down Expand Up @@ -1135,7 +1133,29 @@ where
})?
};

let total_weight = parent_weight + pre_digest.added_weight();
let added_weight = {
let global_randomness = find_global_randomness_descriptor(&block.header)
.expect("Verification of the header was done before this; qed")
.expect("Verification of the header was done before this; qed")
.global_randomness;
let global_challenge =
derive_global_challenge(&global_randomness, pre_digest.slot.into());

// Verification of the local challenge was done before this
let target = u64::from_be_bytes(
derive_target(
&PublicKey::from_bytes(pre_digest.solution.public_key.as_ref())
.expect("Always correct length; qed"),
global_challenge,
&pre_digest.solution.local_challenge,
)
.expect("Verification of the local challenge was done before this; qed"),
);
let tag = u64::from_be_bytes(pre_digest.solution.tag);

u128::from(u64::MAX - subspace_core_primitives::bidirectional_distance(&target, &tag))
};
let total_weight = parent_weight + added_weight;

let info = self.client.info();

Expand Down Expand Up @@ -1311,7 +1331,6 @@ where
slot_now,
telemetry,
client,
solution_signing_context: schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT),
reward_signing_context: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
block: PhantomData::default(),
};
Expand Down
Loading

0 comments on commit 640f1c8

Please sign in to comment.