mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add yParity to rpc signatures (#3800)
This commit is contained in:
@ -182,6 +182,8 @@ impl Transaction {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::eth::transaction::signature::Parity;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@ -198,7 +200,12 @@ mod tests {
|
||||
gas_price: Some(U128::from(9)),
|
||||
gas: U256::from(10),
|
||||
input: Bytes::from(vec![11, 12, 13]),
|
||||
signature: Some(Signature { v: U256::from(14), r: U256::from(14), s: U256::from(14) }),
|
||||
signature: Some(Signature {
|
||||
v: U256::from(14),
|
||||
r: U256::from(14),
|
||||
s: U256::from(14),
|
||||
y_parity: None,
|
||||
}),
|
||||
chain_id: Some(U64::from(17)),
|
||||
access_list: None,
|
||||
transaction_type: Some(U64::from(20)),
|
||||
@ -213,4 +220,39 @@ mod tests {
|
||||
let deserialized: Transaction = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(transaction, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_transaction_with_parity_bit() {
|
||||
let transaction = Transaction {
|
||||
hash: H256::from_low_u64_be(1),
|
||||
nonce: U256::from(2),
|
||||
block_hash: Some(H256::from_low_u64_be(3)),
|
||||
block_number: Some(U256::from(4)),
|
||||
transaction_index: Some(U256::from(5)),
|
||||
from: Address::from_low_u64_be(6),
|
||||
to: Some(Address::from_low_u64_be(7)),
|
||||
value: U256::from(8),
|
||||
gas_price: Some(U128::from(9)),
|
||||
gas: U256::from(10),
|
||||
input: Bytes::from(vec![11, 12, 13]),
|
||||
signature: Some(Signature {
|
||||
v: U256::from(14),
|
||||
r: U256::from(14),
|
||||
s: U256::from(14),
|
||||
y_parity: Some(Parity(true)),
|
||||
}),
|
||||
chain_id: Some(U64::from(17)),
|
||||
access_list: None,
|
||||
transaction_type: Some(U64::from(20)),
|
||||
max_fee_per_gas: Some(U128::from(21)),
|
||||
max_priority_fee_per_gas: Some(U128::from(22)),
|
||||
};
|
||||
let serialized = serde_json::to_string(&transaction).unwrap();
|
||||
assert_eq!(
|
||||
serialized,
|
||||
r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","yParity":"0x1","chainId":"0x11","type":"0x14"}"#
|
||||
);
|
||||
let deserialized: Transaction = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(transaction, deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ pub struct Signature {
|
||||
pub r: U256,
|
||||
/// The S field of the signature; the point on the curve.
|
||||
pub s: U256,
|
||||
// TODO: change these fields to an untagged enum for `v` XOR `y_parity` if/when CLs support it.
|
||||
// See <https://github.com/ethereum/go-ethereum/issues/27727> for more information
|
||||
/// For EIP-155, EIP-2930 and Blob transactions this is set to the parity (0 for even, 1 for
|
||||
/// odd) of the y-value of the secp256k1 signature.
|
||||
///
|
||||
@ -16,6 +18,9 @@ pub struct Signature {
|
||||
///
|
||||
/// See also <https://ethereum.github.io/execution-apis/api-documentation/> and <https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash>
|
||||
pub v: U256,
|
||||
/// The y parity of the signature. This is only used for typed (non-legacy) transactions.
|
||||
#[serde(default, rename = "yParity", skip_serializing_if = "Option::is_none")]
|
||||
pub y_parity: Option<Parity>,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
@ -28,14 +33,24 @@ impl Signature {
|
||||
signature: PrimitiveSignature,
|
||||
chain_id: Option<u64>,
|
||||
) -> Self {
|
||||
Self { r: signature.r, s: signature.s, v: U256::from(signature.v(chain_id)) }
|
||||
Self {
|
||||
r: signature.r,
|
||||
s: signature.s,
|
||||
v: U256::from(signature.v(chain_id)),
|
||||
y_parity: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new rpc signature from a non-legacy [primitive
|
||||
/// signature](reth_primitives::Signature). This sets the `v` value to `0` or `1` depending on
|
||||
/// the signature's `odd_y_parity`.
|
||||
pub(crate) fn from_typed_primitive_signature(signature: PrimitiveSignature) -> Self {
|
||||
Self { r: signature.r, s: signature.s, v: U256::from(signature.odd_y_parity as u8) }
|
||||
Self {
|
||||
r: signature.r,
|
||||
s: signature.s,
|
||||
v: U256::from(signature.odd_y_parity as u8),
|
||||
y_parity: Some(Parity(signature.odd_y_parity)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new rpc signature from a legacy [primitive
|
||||
@ -58,3 +73,168 @@ impl Signature {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type that represents the signature parity byte, meant for use in RPC.
|
||||
///
|
||||
/// This will be serialized as "0x0" if false, and "0x1" if true.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Parity(
|
||||
#[serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity")] pub bool,
|
||||
);
|
||||
|
||||
fn serialize_parity<S>(parity: &bool, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(if *parity { "0x1" } else { "0x0" })
|
||||
}
|
||||
|
||||
/// This implementation disallows serialization of the y parity bit that are not `"0x0"` or `"0x1"`.
|
||||
fn deserialize_parity<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
"0x0" => Ok(false),
|
||||
"0x1" => Ok(true),
|
||||
_ => Err(serde::de::Error::custom(format!(
|
||||
"invalid parity value, parity should be either \"0x0\" or \"0x1\": {}",
|
||||
s
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize_without_parity() {
|
||||
let raw_signature_without_y_parity = r#"{
|
||||
"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0",
|
||||
"s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05",
|
||||
"v":"0x1"
|
||||
}"#;
|
||||
|
||||
let signature: Signature = serde_json::from_str(raw_signature_without_y_parity).unwrap();
|
||||
let expected = Signature {
|
||||
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
|
||||
.unwrap(),
|
||||
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
|
||||
.unwrap(),
|
||||
v: U256::from_str("1").unwrap(),
|
||||
y_parity: None,
|
||||
};
|
||||
|
||||
assert_eq!(signature, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_with_parity() {
|
||||
let raw_signature_with_y_parity = r#"{
|
||||
"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0",
|
||||
"s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05",
|
||||
"v":"0x1",
|
||||
"yParity": "0x1"
|
||||
}"#;
|
||||
|
||||
let signature: Signature = serde_json::from_str(raw_signature_with_y_parity).unwrap();
|
||||
let expected = Signature {
|
||||
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
|
||||
.unwrap(),
|
||||
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
|
||||
.unwrap(),
|
||||
v: U256::from_str("1").unwrap(),
|
||||
y_parity: Some(Parity(true)),
|
||||
};
|
||||
|
||||
assert_eq!(signature, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_both_parity() {
|
||||
// this test should be removed if the struct moves to an enum based on tx type
|
||||
let signature = Signature {
|
||||
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
|
||||
.unwrap(),
|
||||
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
|
||||
.unwrap(),
|
||||
v: U256::from_str("1").unwrap(),
|
||||
y_parity: Some(Parity(true)),
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&signature).unwrap();
|
||||
assert_eq!(
|
||||
serialized,
|
||||
r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","v":"0x1","yParity":"0x1"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_v_only() {
|
||||
// this test should be removed if the struct moves to an enum based on tx type
|
||||
let signature = Signature {
|
||||
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
|
||||
.unwrap(),
|
||||
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
|
||||
.unwrap(),
|
||||
v: U256::from_str("1").unwrap(),
|
||||
y_parity: None,
|
||||
};
|
||||
|
||||
let expected = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","v":"0x1"}"#;
|
||||
|
||||
let serialized = serde_json::to_string(&signature).unwrap();
|
||||
assert_eq!(serialized, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_parity() {
|
||||
let parity = Parity(true);
|
||||
let serialized = serde_json::to_string(&parity).unwrap();
|
||||
assert_eq!(serialized, r#""0x1""#);
|
||||
|
||||
let parity = Parity(false);
|
||||
let serialized = serde_json::to_string(&parity).unwrap();
|
||||
assert_eq!(serialized, r#""0x0""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_parity() {
|
||||
let raw_parity = r#""0x1""#;
|
||||
let parity: Parity = serde_json::from_str(raw_parity).unwrap();
|
||||
assert_eq!(parity, Parity(true));
|
||||
|
||||
let raw_parity = r#""0x0""#;
|
||||
let parity: Parity = serde_json::from_str(raw_parity).unwrap();
|
||||
assert_eq!(parity, Parity(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_parity_invalid() {
|
||||
let raw_parity = r#""0x2""#;
|
||||
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
|
||||
assert!(parity.is_err());
|
||||
|
||||
let raw_parity = r#""0x""#;
|
||||
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
|
||||
assert!(parity.is_err());
|
||||
|
||||
// In the spec this is defined as a uint, which requires 0x
|
||||
// yParity:
|
||||
// <https://github.com/ethereum/execution-apis/blob/8fcafbbc86257f6e61fddd9734148e38872a71c9/src/schemas/transaction.yaml#L157>
|
||||
//
|
||||
// uint:
|
||||
// <https://github.com/ethereum/execution-apis/blob/8fcafbbc86257f6e61fddd9734148e38872a71c9/src/schemas/base-types.yaml#L47>
|
||||
let raw_parity = r#""1""#;
|
||||
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
|
||||
assert!(parity.is_err());
|
||||
|
||||
let raw_parity = r#""0""#;
|
||||
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
|
||||
assert!(parity.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user