diff --git a/Cargo.lock b/Cargo.lock index 3bfc244b5553d..56e6cfb587784 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1174,19 +1174,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "curve25519-dalek-ng" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" -dependencies = [ - "byteorder 1.4.3", - "digest 0.9.0", - "rand_core 0.6.3", - "subtle-ng", - "zeroize", -] - [[package]] name = "data-encoding" version = "2.3.2" @@ -3539,18 +3526,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder 1.4.3", - "keccak", - "rand_core 0.6.3", - "zeroize", -] - [[package]] name = "minicbor" version = "0.8.1" @@ -4063,7 +4038,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "schnorrkel 0.10.2", + "schnorrkel", "sp-consensus-subspace", "sp-core", "sp-io", @@ -4105,7 +4080,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "schnorrkel 0.9.1", + "schnorrkel", "sp-consensus-slots", "sp-consensus-subspace", "sp-core", @@ -5460,7 +5435,7 @@ dependencies = [ "sc-service", "sc-telemetry", "sc-utils", - "schnorrkel 0.9.1", + "schnorrkel", "serde", "sp-api", "sp-block-builder", @@ -6086,7 +6061,7 @@ dependencies = [ "arrayvec 0.5.2", "curve25519-dalek 2.1.3", "getrandom 0.1.16", - "merlin 2.0.1", + "merlin", "rand 0.7.3", "rand_core 0.5.1", "sha2 0.8.2", @@ -6094,23 +6069,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "schnorrkel" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" -dependencies = [ - "arrayref", - "arrayvec 0.7.2", - "curve25519-dalek-ng", - "merlin 3.0.0", - "rand_core 0.6.3", - "serde_bytes", - "sha2 0.9.8", - "subtle-ng", - "zeroize", -] - [[package]] name = "scoped-tls" version = "1.0.0" @@ -6217,15 +6175,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_bytes" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.130" @@ -6614,7 +6563,7 @@ version = "0.10.0-dev" source = "git+https://github.com/paritytech/substrate?rev=26d69bcbe26f6b463e9374e1b1c54c3067fb6131#26d69bcbe26f6b463e9374e1b1c54c3067fb6131" dependencies = [ "async-trait", - "merlin 2.0.1", + "merlin", "parity-scale-codec", "scale-info", "serde", @@ -6668,7 +6617,7 @@ version = "0.10.0-dev" source = "git+https://github.com/paritytech/substrate?rev=26d69bcbe26f6b463e9374e1b1c54c3067fb6131#26d69bcbe26f6b463e9374e1b1c54c3067fb6131" dependencies = [ "parity-scale-codec", - "schnorrkel 0.9.1", + "schnorrkel", "sp-core", "sp-runtime", "sp-std", @@ -6693,7 +6642,7 @@ dependencies = [ "lazy_static", "libsecp256k1 0.6.0", "log", - "merlin 2.0.1", + "merlin", "num-traits", "parity-scale-codec", "parity-util-mem", @@ -6702,7 +6651,7 @@ dependencies = [ "rand 0.7.3", "regex", "scale-info", - "schnorrkel 0.9.1", + "schnorrkel", "secrecy", "serde", "sha2 0.9.8", @@ -6826,10 +6775,10 @@ dependencies = [ "async-trait", "derive_more", "futures 0.3.17", - "merlin 2.0.1", + "merlin", "parity-scale-codec", "parking_lot", - "schnorrkel 0.9.1", + "schnorrkel", "serde", "sp-core", "sp-externalities", @@ -7266,7 +7215,7 @@ dependencies = [ "rand 0.8.4", "rayon", "rocksdb", - "schnorrkel 0.10.2", + "schnorrkel", "serde", "serde_json", "subspace-archiving", @@ -7370,6 +7319,7 @@ name = "subspace-solving" version = "0.1.0" dependencies = [ "rand 0.8.4", + "schnorrkel", "sha2 0.9.8", "sloth256-189", "subspace-core-primitives", @@ -7383,7 +7333,7 @@ checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", - "schnorrkel 0.9.1", + "schnorrkel", "sha2 0.9.8", "zeroize", ] @@ -7563,12 +7513,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "subtle-ng" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" - [[package]] name = "syn" version = "1.0.81" diff --git a/crates/pallet-offences-subspace/Cargo.toml b/crates/pallet-offences-subspace/Cargo.toml index dc8692560c325..1fdaed5dbd2c9 100644 --- a/crates/pallet-offences-subspace/Cargo.toml +++ b/crates/pallet-offences-subspace/Cargo.toml @@ -25,7 +25,7 @@ sp-std = { version = "4.0.0-dev", default-features = false, git = "https://githu [dev-dependencies] sp-io = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "26d69bcbe26f6b463e9374e1b1c54c3067fb6131" } sp-core = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "26d69bcbe26f6b463e9374e1b1c54c3067fb6131" } -schnorrkel = "0.10.1" +schnorrkel = "0.9.1" [features] default = ["std"] diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index a540d075c7868..ab215cfc02cdf 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -35,7 +35,7 @@ use sp_runtime::{ Perbill, }; use subspace_core_primitives::{ - ArchivedBlockProgress, LastArchivedBlock, Piece, RootBlock, Sha256Hash, Tag, + ArchivedBlockProgress, LastArchivedBlock, Piece, RootBlock, Sha256Hash, Signature, Tag, }; use subspace_solving::{SubspaceCodec, SOLUTION_SIGNING_CONTEXT}; @@ -194,17 +194,18 @@ pub fn go_to_block(keypair: &Keypair, block: u64, slot: u64) { let subspace_solving = SubspaceCodec::new(&keypair.public); let ctx = schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT); let piece_index = 0; - let mut piece = Piece::default(); - subspace_solving.encode(piece_index, &mut piece).unwrap(); - let tag: Tag = subspace_solving::create_tag(&piece, Subspace::salt().to_le_bytes()); + let mut encoding = Piece::default(); + subspace_solving.encode(piece_index, &mut encoding).unwrap(); + let tag: Tag = subspace_solving::create_tag(&encoding, Subspace::salt().to_le_bytes()); let pre_digest = make_pre_digest( slot.into(), Solution { public_key: FarmerPublicKey::from_slice(&keypair.public.to_bytes()), piece_index: 0, - encoding: piece.to_vec(), - signature: keypair.sign(ctx.bytes(&tag)).to_bytes().to_vec(), + encoding, + signature: keypair.sign(ctx.bytes(&tag)).to_bytes().into(), + local_challenge: Signature::default(), tag, }, ); @@ -254,7 +255,7 @@ pub fn generate_equivocation_proof( let tag: Tag = [(current_block % 8) as u8; 8]; let public_key = FarmerPublicKey::from_slice(&keypair.public.to_bytes()); - let signature = keypair.sign(ctx.bytes(&tag)).to_bytes().to_vec(); + let signature = keypair.sign(ctx.bytes(&tag)).to_bytes(); let make_header = |piece_index| { let parent_hash = System::parent_hash(); @@ -263,8 +264,9 @@ pub fn generate_equivocation_proof( Solution { public_key: public_key.clone(), piece_index, - encoding: encoding.to_vec(), - signature: signature.clone(), + encoding, + signature: signature.into(), + local_challenge: Signature::default(), tag, }, ); diff --git a/crates/sc-consensus-subspace-rpc/src/lib.rs b/crates/sc-consensus-subspace-rpc/src/lib.rs index 86c90b098f8ce..50102ab4d3771 100644 --- a/crates/sc-consensus-subspace-rpc/src/lib.rs +++ b/crates/sc-consensus-subspace-rpc/src/lib.rs @@ -304,6 +304,7 @@ where piece_index: solution.piece_index, encoding: solution.encoding, signature: solution.signature, + local_challenge: solution.local_challenge, tag: solution.tag, }; @@ -326,7 +327,7 @@ where // This will be sent to the farmer Ok(Ok(SlotInfo { slot_number: new_slot_info.slot.into(), - challenge: new_slot_info.challenge, + global_challenge: new_slot_info.global_challenge, salt: new_slot_info.salt, next_salt: new_slot_info.next_salt, solution_range: new_slot_info.solution_range, diff --git a/crates/sc-consensus-subspace/src/authorship.rs b/crates/sc-consensus-subspace/src/authorship.rs index 83132785ebc14..373a6357068e7 100644 --- a/crates/sc-consensus-subspace/src/authorship.rs +++ b/crates/sc-consensus-subspace/src/authorship.rs @@ -136,7 +136,7 @@ where let new_slot_info = NewSlotInfo { slot, - challenge: subspace_solving::derive_global_challenge(&epoch_randomness, slot), + global_challenge: subspace_solving::derive_global_challenge(&epoch_randomness, slot), salt: salt.to_le_bytes(), // TODO: This will not be the correct way in the future once salt is no longer // just an incremented number diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index aaaa1fe4966d4..c0104acb05a8b 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -113,7 +113,7 @@ use std::{borrow::Cow, collections::HashMap, pin::Pin, sync::Arc, time::Duration pub use subspace_archiving::archiver::ArchivedSegment; use subspace_archiving::archiver::{BlockArchiver, ObjectArchiver}; use subspace_archiving::pre_genesis_data; -use subspace_core_primitives::{Randomness, RootBlock, Salt}; +use subspace_core_primitives::{Randomness, RootBlock, Salt, Tag}; use subspace_solving::SOLUTION_SIGNING_CONTEXT; mod archiver; @@ -130,8 +130,8 @@ mod verification; pub struct NewSlotInfo { /// Slot pub slot: Slot, - /// Slot challenge - pub challenge: [u8; 8], + /// Global slot challenge + pub global_challenge: Tag, /// Salt pub salt: Salt, /// Salt for the next eon @@ -264,12 +264,12 @@ pub enum Error { /// Bad solution signature #[display(fmt = "Bad solution signature on slot {:?}: {:?}", _0, _1)] BadSolutionSignature(Slot, schnorrkel::SignatureError), + /// Bad local challenge + #[display(fmt = "Local challenge is invalid for slot {}: {}", _0, _1)] + BadLocalChallenge(Slot, schnorrkel::SignatureError), /// Solution is outside of solution range #[display(fmt = "Solution is outside of solution range for slot {}", _0)] OutsideOfSolutionRange(Slot), - /// Encoding is of wrong size - #[display(fmt = "Encoding is of the wrong size")] - EncodingOfWrongSize, /// Invalid encoding of a piece #[display(fmt = "Invalid encoding for slot {}", _0)] InvalidEncoding(Slot), diff --git a/crates/sc-consensus-subspace/src/tests.rs b/crates/sc-consensus-subspace/src/tests.rs index 92e76f8bff1f5..cf9bb59a7c034 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, Tag}; +use subspace_core_primitives::{Piece, Signature, Tag}; use subspace_solving::SubspaceCodec; use substrate_test_runtime::{Block as TestBlock, Hash}; @@ -583,7 +583,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static let keypair = Keypair::generate(); let subspace_solving = SubspaceCodec::new(&keypair.public); let ctx = schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT); - let (piece_index, mut piece) = archived_pieces_receiver + let (piece_index, mut encoding) = archived_pieces_receiver .await .unwrap() .into_iter() @@ -591,7 +591,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static .choose(&mut rand::thread_rng()) .map(|(piece_index, piece)| (piece_index as u64, piece)) .unwrap(); - subspace_solving.encode(piece_index, &mut piece).unwrap(); + subspace_solving.encode(piece_index, &mut encoding).unwrap(); while let Some(NewSlotNotification { new_slot_info, @@ -599,15 +599,19 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static }) = new_slot_notification_stream.next().await { if Into::::into(new_slot_info.slot) % 3 == (*peer_id) as u64 { - let tag: Tag = subspace_solving::create_tag(&piece, new_slot_info.salt); + let tag: Tag = subspace_solving::create_tag(&encoding, new_slot_info.salt); let _ = solution_sender .send(( Solution { public_key: FarmerPublicKey::from_slice(&keypair.public.to_bytes()), piece_index, - encoding: piece.to_vec(), - signature: keypair.sign(ctx.bytes(&tag)).to_bytes().to_vec(), + encoding, + signature: keypair.sign(ctx.bytes(&tag)).to_bytes().into(), + local_challenge: keypair + .sign(ctx.bytes(&new_slot_info.global_challenge)) + .to_bytes() + .into(), tag, }, keypair.secret.to_bytes().into(), @@ -718,11 +722,12 @@ pub fn dummy_claim_slot(slot: Slot, _epoch: &Epoch) -> Option<(PreDigest, Farmer return Some(( PreDigest { solution: Solution { - public_key: Default::default(), + public_key: FarmerPublicKey::default(), piece_index: 0, - encoding: vec![], - signature: vec![], - tag: Default::default(), + encoding: Piece::default(), + signature: Signature::default(), + local_challenge: Signature::default(), + tag: Tag::default(), }, slot, }, @@ -783,7 +788,7 @@ fn propose_and_import_block( let encoding = Piece::default(); let tag: Tag = [0u8; 8]; - let signature = keypair.sign(ctx.bytes(&tag)).to_bytes().to_vec(); + let signature = keypair.sign(ctx.bytes(&tag)).to_bytes(); ( sp_runtime::generic::Digest { @@ -792,8 +797,9 @@ fn propose_and_import_block( solution: Solution { public_key: FarmerPublicKey::from_slice(&keypair.public.to_bytes()), piece_index: 0, - encoding: encoding.to_vec(), - signature: signature.clone(), + encoding, + signature: signature.into(), + local_challenge: Signature::default(), tag, }, })], @@ -820,7 +826,7 @@ fn propose_and_import_block( .unwrap() .unwrap(); - let seal = Item::subspace_seal(signature.try_into().unwrap()); + let seal = Item::subspace_seal(signature.to_vec().try_into().unwrap()); let post_hash = { block.header.digest_mut().push(seal.clone()); diff --git a/crates/sc-consensus-subspace/src/verification.rs b/crates/sc-consensus-subspace/src/verification.rs index 6ea7827cd6f65..06f8a05303f61 100644 --- a/crates/sc-consensus-subspace/src/verification.rs +++ b/crates/sc-consensus-subspace/src/verification.rs @@ -25,8 +25,10 @@ use sp_consensus_subspace::digests::{CompatibleDigestItem, PreDigest, Solution}; use sp_core::Public; use sp_runtime::{traits::DigestItemFor, traits::Header, RuntimeAppPublic}; use subspace_archiving::archiver; -use subspace_core_primitives::{crypto, Piece, Randomness, Salt, Sha256Hash}; -use subspace_solving::{derive_global_challenge, derive_local_challenge, SubspaceCodec}; +use subspace_core_primitives::{Randomness, Salt, Sha256Hash}; +use subspace_solving::{ + derive_global_challenge, is_local_challenge_valid, SubspaceCodec, TAG_SIZE, +}; /// Subspace verification parameters pub(super) struct VerificationParams<'a, B: 'a + BlockT> { @@ -154,13 +156,7 @@ fn check_signature( /// Check if the tag of a solution's piece is valid. fn check_piece_tag(slot: Slot, salt: Salt, solution: &Solution) -> Result<(), Error> { - let piece: Piece = solution - .encoding - .as_slice() - .try_into() - .map_err(|_error| Error::EncodingOfWrongSize)?; - - if !subspace_solving::is_tag_valid(&piece, salt, solution.tag) { + if !subspace_solving::is_tag_valid(&solution.encoding, salt, solution.tag) { return Err(Error::InvalidTag(slot)); } @@ -178,7 +174,7 @@ fn check_piece( ) -> Result<(), Error> { check_piece_tag(slot, salt, solution)?; - let mut piece = solution.encoding.clone(); + let mut piece = solution.encoding; // Ensure piece is decodable. let subspace_codec = SubspaceCodec::new(&solution.public_key); @@ -199,15 +195,12 @@ fn check_piece( } /// Returns true if `solution.tag` is within the solution range. -fn is_within_solution_range( - solution: &Solution, - global_challenge: [u8; 8], - solution_range: u64, -) -> bool { - let farmer_public_key_hash = crypto::sha256_hash(&solution.public_key); - let local_challenge = derive_local_challenge(global_challenge, farmer_public_key_hash); - - let target = u64::from_be_bytes(local_challenge); +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 (lower, is_lower_overflowed) = target.overflowing_sub(solution_range / 2); let (upper, is_upper_overflowed) = target.overflowing_add(solution_range / 2); @@ -246,11 +239,15 @@ pub(crate) fn verify_solution( signing_context, } = params; - if !is_within_solution_range( - solution, + if let Err(error) = is_local_challenge_valid( derive_global_challenge(epoch_randomness, slot), - solution_range, + &solution.local_challenge, + &solution.public_key, ) { + return Err(Error::BadLocalChallenge(slot, error)); + } + + if !is_within_solution_range(solution, solution_range) { return Err(Error::OutsideOfSolutionRange(slot)); } diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index 18bf42bc6cbf3..9e6bdc1aa987d 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -24,8 +24,7 @@ use codec::{Codec, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_consensus_slots::Slot; use sp_runtime::{DigestItem, RuntimeDebug}; -use sp_std::vec::Vec; -use subspace_core_primitives::{Randomness, Tag}; +use subspace_core_primitives::{Piece, Randomness, Signature, Tag}; // TODO: better documentation here /// Solution @@ -36,9 +35,11 @@ pub struct Solution { /// Index of encoded piece pub piece_index: u64, /// Encoding - pub encoding: Vec, + pub encoding: Piece, /// Signature of the tag - pub signature: Vec, + pub signature: Signature, + /// Local challenge derived with farmer's identity + pub local_challenge: Signature, /// Tag (hmac of encoding and salt) pub tag: Tag, } @@ -49,9 +50,10 @@ impl Solution { Self { public_key: FarmerPublicKey::default(), piece_index: 0u64, - encoding: Vec::new(), - signature: Vec::new(), - tag: [0u8; 8], + encoding: Piece::default(), + signature: Signature::default(), + local_challenge: Signature::default(), + tag: Tag::default(), } } } diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index 867a1c6bb0313..643439d1e06ec 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -55,6 +55,82 @@ pub type Tag = [u8; 8]; /// Salt used for creating commitment tags for pieces. pub type Salt = [u8; 8]; +const PUBLIC_KEY_LENGTH: usize = 32; + +/// A Ristretto Schnorr public key as bytes produced by `schnorrkel` crate. +#[derive(Default, 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 PublicKey([u8; PUBLIC_KEY_LENGTH]); + +impl From<[u8; PUBLIC_KEY_LENGTH]> for PublicKey { + fn from(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Self { + Self(bytes) + } +} + +impl From for [u8; PUBLIC_KEY_LENGTH] { + fn from(signature: PublicKey) -> Self { + signature.0 + } +} + +impl Deref for PublicKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for PublicKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +const SIGNATURE_LENGTH: usize = 64; + +/// 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 Signature( + #[cfg_attr(feature = "std", serde(with = "serde_arrays"))] [u8; SIGNATURE_LENGTH], +); + +impl Default for Signature { + fn default() -> Self { + Self([0u8; SIGNATURE_LENGTH]) + } +} + +impl From<[u8; SIGNATURE_LENGTH]> for Signature { + fn from(bytes: [u8; SIGNATURE_LENGTH]) -> Self { + Self(bytes) + } +} + +impl From for [u8; SIGNATURE_LENGTH] { + fn from(signature: Signature) -> Self { + signature.0 + } +} + +impl Deref for Signature { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + /// 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/Cargo.toml b/crates/subspace-farmer/Cargo.toml index 1780cf35e66b9..399f6d5520266 100644 --- a/crates/subspace-farmer/Cargo.toml +++ b/crates/subspace-farmer/Cargo.toml @@ -29,7 +29,7 @@ log = "0.4.14" lru = "0.6.6" parity-scale-codec = "2.3.0" rayon = "1.5.0" -schnorrkel = "0.10.1" +schnorrkel = "0.9.1" serde = { version = "1.0.125", features = ["derive"] } serde_json = "1.0.64" subspace-archiving = { version = "0.1.0", path = "../subspace-archiving" } diff --git a/crates/subspace-farmer/src/farming.rs b/crates/subspace-farmer/src/farming.rs index e1c537e2399a4..d2cb047afb673 100644 --- a/crates/subspace-farmer/src/farming.rs +++ b/crates/subspace-farmer/src/farming.rs @@ -6,8 +6,9 @@ use anyhow::Result; use futures::{future, future::Either}; use log::{debug, error, info, trace}; use std::time::Instant; -use subspace_core_primitives::{crypto, Salt}; +use subspace_core_primitives::{Salt, Signature}; 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 @@ -90,8 +91,6 @@ async fn subscribe_to_slot_info( commitments: &Commitments, identity: &Identity, ) -> Result<()> { - let farmer_public_key_hash = crypto::sha256_hash(&identity.public_key()); - info!("Subscribing to slot info"); let mut new_slots = client.subscribe_slot_info().await?; @@ -102,22 +101,28 @@ async fn subscribe_to_slot_info( update_commitments(plot, commitments, &mut salts, &slot_info); - let local_challenge = - subspace_solving::derive_local_challenge(slot_info.challenge, &farmer_public_key_hash); + let local_challenge = derive_local_challenge(slot_info.global_challenge, identity); let maybe_solution = match commitments - .find_by_range(local_challenge, slot_info.solution_range, slot_info.salt) + .find_by_range( + local_challenge[..TAG_SIZE] + .try_into() + .expect("Signature is always bigger than tag; qed"), + slot_info.solution_range, + slot_info.salt, + ) .await { Some((tag, piece_index)) => { let encoding = plot.read(piece_index).await?; - let solution = Solution::new( - identity.public_key().to_bytes(), + let solution = Solution { + public_key: identity.public_key().to_bytes().into(), piece_index, - encoding.to_vec(), - identity.sign(&tag).to_bytes().to_vec(), + encoding, + signature: identity.sign(&tag).to_bytes().into(), + local_challenge, tag, - ); + }; debug!("Solution found"); trace!("Solution found: {:?}", solution); @@ -218,3 +223,8 @@ fn update_commitments( } } } + +/// Derive local challenge for farmer's identity from the global challenge. +fn derive_local_challenge>(global_challenge: C, identity: &Identity) -> Signature { + 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 566391cd2c473..cca3c1a859bc2 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::{Salt, Tag}; +use subspace_core_primitives::{Piece, PublicKey, Salt, Signature, Tag}; /// Type of a slot number. pub type SlotNumber = u64; @@ -57,11 +57,12 @@ pub struct FarmerMetadata { /// Information about new slot that just arrived #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SlotInfo { /// Slot number pub slot_number: SlotNumber, - /// Slot challenge - pub challenge: [u8; 8], + /// Global slot challenge + pub global_challenge: Tag, /// Salt pub salt: Salt, /// Salt for the next eon @@ -73,6 +74,7 @@ pub struct SlotInfo { /// Response of a slot challenge consisting of an optional solution and /// the submitter(farmer)'s secret key for block signing. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SolutionResponse { /// Slot number. pub slot_number: SlotNumber, @@ -86,39 +88,24 @@ pub struct SolutionResponse { pub secret_key: Vec, } +// TODO: Deduplicate this type /// Duplicate type of [sp_consensus_subspace::digests::Solution] as we'd like to -/// not pull in the Substrate libraries when it only relateds to the Subspace functionalities. +/// not pull in the Substrate libraries when it only related to the Subspace functionalities. /// /// [sp_consensus_subspace::digests::Solution]: ../sp_consensus_subspace/digests/struct.Solution.html #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Solution { /// Public key of the farmer that created the solution - pub public_key: [u8; 32], + pub public_key: PublicKey, /// Index of encoded piece pub piece_index: u64, /// Encoding - pub encoding: Vec, + pub encoding: Piece, /// Signature of the tag - pub signature: Vec, + pub signature: Signature, + /// Local challenge derived with farmer's identity + pub local_challenge: Signature, /// Tag (hmac of encoding and salt) pub tag: Tag, } - -impl Solution { - /// Creates a new instance of [`Solution`]. - pub fn new( - public_key: [u8; 32], - piece_index: u64, - encoding: Vec, - signature: Vec, - tag: Tag, - ) -> Self { - Self { - public_key, - piece_index, - encoding, - signature, - tag, - } - } -} diff --git a/crates/subspace-solving/Cargo.toml b/crates/subspace-solving/Cargo.toml index 42e9cb42ffce3..8c3d052d3dcab 100644 --- a/crates/subspace-solving/Cargo.toml +++ b/crates/subspace-solving/Cargo.toml @@ -13,6 +13,7 @@ include = [ ] [dependencies] +schnorrkel = "0.9.1" sloth256-189 = "0.2.2" subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primitives" } diff --git a/crates/subspace-solving/src/lib.rs b/crates/subspace-solving/src/lib.rs index 08cb8fc3a5080..de0b8023a7fa6 100644 --- a/crates/subspace-solving/src/lib.rs +++ b/crates/subspace-solving/src/lib.rs @@ -22,8 +22,9 @@ mod codec; pub use codec::SubspaceCodec; +use schnorrkel::SignatureResult; use sha2::{Digest, Sha256}; -use subspace_core_primitives::{crypto, Piece, Randomness, Salt, Tag}; +use subspace_core_primitives::{crypto, Piece, Randomness, Salt, Signature, Tag}; /// Signing context used for creating solution signatures by farmer pub const SOLUTION_SIGNING_CONTEXT: &[u8] = b"FARMER"; @@ -54,15 +55,15 @@ pub fn derive_global_challenge>(epoch_randomness: &Randomness, s .expect("Slice is always of correct size; qed") } -/// Derive local challenge for farmer's public key hash from the global challenge. -pub fn derive_local_challenge, H: AsRef<[u8]>>( - global_challenge: C, - farmer_public_key_hash: H, -) -> Tag { - let mut hasher = Sha256::new(); - hasher.update(global_challenge.as_ref()); - hasher.update(farmer_public_key_hash.as_ref()); - hasher.finalize()[..TAG_SIZE] - .try_into() - .expect("Slice is always of correct size; qed") +/// 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, + public_key: P, +) -> SignatureResult<()> { + let signature = schnorrkel::Signature::from_bytes(local_challenge)?; + let public_key = schnorrkel::PublicKey::from_bytes(public_key.as_ref())?; + + let ctx = schnorrkel::context::signing_context(SOLUTION_SIGNING_CONTEXT); + public_key.verify(ctx.bytes(&global_challenge), &signature) }