-
Notifications
You must be signed in to change notification settings - Fork 77
test: add SSZ test vectors #1215
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
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
6ef6284
add ssz test vectors
unnawut 5a5df5b
change Signature.inner to hold leansig's signature type directly inst…
unnawut 230e188
fix ssz tests
unnawut 68efe49
fix linting
unnawut 32a7f8c
fix linting
unnawut aca1153
fix linting
unnawut 48aa070
simplify ssz_test.rs
unnawut eef7fee
remove unnecessary conversions
unnawut afc6d4f
fix comments
unnawut bba40bb
remove TreeHash checks from SSZ tests
unnawut e37e5ff
simplify ssz test setup
unnawut 5b35536
deduplicate conversions with macros
unnawut 539f02d
fix linting
unnawut 1d28707
better passthrough_conversion naming
unnawut 3e554fe
refactor to custom_conversion macro
unnawut 9e95a6d
lint lint lint
unnawut 22ba388
fix slow Signature::blank()
unnawut e89b286
blank signatures
unnawut fb80946
Add regression tests
KolbyML 4013c95
Clean up PR
KolbyML File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,59 @@ | ||
| use alloy_primitives::FixedBytes; | ||
| use anyhow::anyhow; | ||
| use leansig::{MESSAGE_LENGTH, serialization::Serializable, signature::SignatureScheme}; | ||
| use leansig::{signature::SignatureScheme, MESSAGE_LENGTH}; | ||
| use serde::{Deserialize, Serialize}; | ||
| use ssz::{Decode, Encode}; | ||
| use ssz_derive::{Decode, Encode}; | ||
| use tree_hash_derive::TreeHash; | ||
|
|
||
| use crate::leansig::{LeanSigScheme, errors::LeanSigError, public_key::PublicKey}; | ||
| use crate::leansig::{public_key::PublicKey, LeanSigScheme}; | ||
|
|
||
| const SIGNATURE_SIZE: usize = 3112; | ||
| /// The inner leansig signature type with built-in SSZ support. | ||
| pub type LeanSigSignature = <LeanSigScheme as SignatureScheme>::Signature; | ||
|
|
||
| type LeanSigSignature = <LeanSigScheme as SignatureScheme>::Signature; | ||
| const BLANK_SIGNATURE_SSZ_BYTES: [u8; 40] = [ | ||
| 36, 0, 0, 0, // offset_path = 36 | ||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // rho (28 zeros) | ||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // | ||
| 40, 0, 0, 0, // offset_hashes = 40 | ||
| 4, 0, 0, 0, // path: empty HashTreeOpening | ||
| ]; | ||
|
|
||
| /// Wrapper around a fixed-size serialized hash-based signature. | ||
| #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, Copy)] | ||
| /// Wrapper around leansig's signature type. | ||
| /// Uses leansig's built-in SSZ encoding for interoperability with other clients. | ||
| #[derive(Clone, Serialize, Deserialize, Encode, Decode)] | ||
| #[ssz(struct_behaviour = "transparent")] | ||
| pub struct Signature { | ||
| pub inner: FixedBytes<SIGNATURE_SIZE>, | ||
| pub inner: LeanSigSignature, | ||
| } | ||
|
|
||
| impl From<&[u8]> for Signature { | ||
| fn from(value: &[u8]) -> Self { | ||
| Self { | ||
| inner: FixedBytes::from_slice(value), | ||
| } | ||
| impl std::fmt::Debug for Signature { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| f.debug_struct("Signature") | ||
| .field("inner", &"<LeanSigSignature>") | ||
| .finish() | ||
| } | ||
| } | ||
|
|
||
| impl Signature { | ||
| pub fn new(inner: FixedBytes<SIGNATURE_SIZE>) -> Self { | ||
| Self { inner } | ||
| impl PartialEq for Signature { | ||
| fn eq(&self, other: &Self) -> bool { | ||
| // Compare by SSZ encoding since LeanSigSignature doesn't implement PartialEq | ||
| self.inner.as_ssz_bytes() == other.inner.as_ssz_bytes() | ||
| } | ||
| } | ||
|
|
||
| impl Eq for Signature {} | ||
|
|
||
| impl Signature { | ||
| /// Create a blank/placeholder signature. | ||
| /// | ||
| /// This decodes from minimal valid SSZ bytes, avoiding expensive key generation. | ||
| /// Only use in contexts where the signature won't be validated. | ||
| pub fn blank() -> Self { | ||
| Self::new(Default::default()) | ||
| Self::from_ssz_bytes(&BLANK_SIGNATURE_SSZ_BYTES).expect("blank signature bytes are valid") | ||
| } | ||
|
|
||
| /// Create a mock signature for testing purposes. | ||
| /// | ||
| /// Note: This generates a real signature which is expensive. Prefer `blank()` when | ||
| /// you just need a placeholder signature. | ||
| pub fn mock() -> Self { | ||
| use rand::rng; | ||
|
Comment on lines
53
to
58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you just remove this function as it isn't used |
||
|
|
||
|
|
@@ -48,15 +67,12 @@ impl Signature { | |
| .expect("Mock signature generation failed") | ||
| } | ||
|
|
||
| pub fn from_lean_sig(signature: LeanSigSignature) -> Result<Self, LeanSigError> { | ||
| Ok(Self { | ||
| inner: FixedBytes::try_from(signature.to_bytes().as_slice())?, | ||
| }) | ||
| pub fn from_lean_sig(signature: LeanSigSignature) -> Self { | ||
| Self { inner: signature } | ||
| } | ||
|
|
||
| pub fn as_lean_sig(&self) -> anyhow::Result<LeanSigSignature> { | ||
| LeanSigSignature::from_bytes(self.inner.as_slice()) | ||
| .map_err(|err| anyhow!("Failed to decode LeanSigSignature from SSZ: {err:?}")) | ||
| pub fn as_lean_sig(&self) -> &LeanSigSignature { | ||
| &self.inner | ||
| } | ||
|
|
||
| pub fn verify( | ||
|
|
@@ -69,17 +85,27 @@ impl Signature { | |
| &public_key.as_lean_sig()?, | ||
| epoch, | ||
| message, | ||
| &self.as_lean_sig()?, | ||
| &self.inner, | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use alloy_primitives::FixedBytes; | ||
| use leansig::serialization::Serializable; | ||
| use rand::rng; | ||
| use ssz::{Decode, Encode}; | ||
|
|
||
| use crate::leansig::{private_key::PrivateKey, signature::Signature}; | ||
|
|
||
| const LEGACY_SIGNATURE_SIZE: usize = 3112; | ||
|
|
||
| #[derive(ssz_derive::Encode)] | ||
| struct LegacySignature { | ||
| inner: FixedBytes<LEGACY_SIGNATURE_SIZE>, | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_serialization_roundtrip() { | ||
| let mut rng = rng(); | ||
|
|
@@ -100,13 +126,32 @@ mod tests { | |
| assert!(result.is_ok(), "Signing should succeed"); | ||
| let signature = result.unwrap(); | ||
|
|
||
| // convert to leansig signature | ||
| let hash_sig_signature = signature.as_lean_sig().unwrap(); | ||
|
|
||
| // convert back to signature | ||
| let signature_returned = Signature::from_lean_sig(hash_sig_signature).unwrap(); | ||
| // SSZ roundtrip test | ||
| let ssz_bytes = signature.as_ssz_bytes(); | ||
| let signature_decoded = Signature::from_ssz_bytes(&ssz_bytes).unwrap(); | ||
|
|
||
| // verify roundtrip | ||
| assert_eq!(signature, signature_returned); | ||
| assert_eq!(signature, signature_decoded); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_ssz_bytes_match_legacy_signature_wrapper() { | ||
| let mut rng = rng(); | ||
| let activation_epoch = 0; | ||
| let num_active_epochs = 10; | ||
|
|
||
| let (_, private_key) = | ||
| PrivateKey::generate_key_pair(&mut rng, activation_epoch, num_active_epochs); | ||
|
|
||
| let epoch = 5; | ||
| let message = [0u8; 32]; | ||
| let signature = private_key.sign(&message, epoch).unwrap(); | ||
|
|
||
| let legacy_signature = LegacySignature { | ||
| inner: FixedBytes::try_from(signature.as_lean_sig().to_bytes().as_slice()) | ||
| .expect("legacy signature bytes should match fixed size"), | ||
| }; | ||
|
|
||
| assert_eq!(legacy_signature.as_ssz_bytes(), signature.as_ssz_bytes()); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| pub mod fork_choice; | ||
| pub mod ssz_test; | ||
| pub mod state_transition; | ||
| pub mod types; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is the right way to do it, but since
FixedBytesis how Ream stores it internally and needs conversion to LeanSigSignature to do any operation anyway, so I'm thinking we could just remove this interim FixedBytes and use LeanSigSignature directlyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will LeanSigSignature encode to FixedBytes though is the question? I will read your code, but that would be the concern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case it's useful this is what I recall. I think FixedBytes was introduced to the spec in devnet0 just because leanSig wasn't stable yet so we just used
FixedBytes<SIGNATURE_SIZE>as the mock Signature type in the specs.And then in devnet1 we started with
Bytes3116in the specs even with the actual signatures.But towards end of devnet1, the
class Signature(Bytes3116)was removed from the specs by leanEthereum/leanSpec#210 and the canonical Signature type becomes thisclass Signature(Container).So since then, leanSig and leanSpec uses the
Signaturecontainer type that's variable length, and the fixed bytes is no longer used. And so that's why I'm thinking we could just useLeanSigSignaturedirectly without conversion to fixed bytes. But I dunno maybe I'm missing something...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I did some regression testing and this looks fine 👍