Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -531,13 +531,115 @@ impl<'de> Deserialize<'de> for EccScalar {
}
}

#[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
#[derive(Clone, Eq, PartialEq, Zeroize, ZeroizeOnDrop)]
pub enum EccScalarBytes {
K256(Box<[u8; 32]>),
P256(Box<[u8; 32]>),
Ed25519(Box<[u8; 32]>),
}

// Helper enum for serialization
//
// TODO add #[serde(with = "serde_bytes")] once we are sure
// that all existing nodes are upgraded to be able to
// understand both formats.
#[derive(Serialize)]
enum EccScalarBytesSerializeHelper<'a> {
K256(&'a [u8; 32]),
P256(#[serde(with = "serde_bytes")] &'a [u8; 32]),
Ed25519(&'a [u8; 32]),
}

// TODO after a sufficient amount of time using bytes as the default
// serialization, remove the explicit Serialize and Deserialize traits
// so that going forward only bytes are supported for decoding
impl Serialize for EccScalarBytes {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::K256(bytes) => {
EccScalarBytesSerializeHelper::K256(bytes.as_ref()).serialize(serializer)
}
Self::P256(bytes) => {
EccScalarBytesSerializeHelper::P256(bytes.as_ref()).serialize(serializer)
}
Self::Ed25519(bytes) => {
EccScalarBytesSerializeHelper::Ed25519(bytes.as_ref()).serialize(serializer)
}
}
}
}

impl<'de> Deserialize<'de> for EccScalarBytes {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::{self, MapAccess, SeqAccess, Visitor};
/// Visitor that can handle both CBOR byte string and array formats
struct BytesOrArrayVisitor;
impl<'de> Visitor<'de> for BytesOrArrayVisitor {
type Value = Box<[u8; 32]>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a byte string or array of 32 bytes")
}
// New efficient format: CBOR byte string
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
if v.len() != 32 {
return Err(E::invalid_length(v.len(), &"32 bytes"));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(v);
Ok(Box::new(arr))
}
// Old format: CBOR array of integers
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut arr = [0u8; 32];
for (i, byte) in arr.iter_mut().enumerate() {
*byte = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(i, &"32 elements"))?;
}
Ok(Box::new(arr))
}
}

struct BytesOrArrayScalar;
impl<'de> de::DeserializeSeed<'de> for BytesOrArrayScalar {
type Value = Box<[u8; 32]>;
fn deserialize<D2: Deserializer<'de>>(
self,
deserializer: D2,
) -> Result<Self::Value, D2::Error> {
deserializer.deserialize_any(BytesOrArrayVisitor)
}
}

struct EccScalarBytesVisitor;

impl<'de> Visitor<'de> for EccScalarBytesVisitor {
type Value = EccScalarBytes;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("EccScalarBytes enum with K256, P256, or Ed25519 variant")
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
let curve_str: String = map
.next_key()?
.ok_or_else(|| de::Error::custom("Expected curve name"))?;
// Use BytesOrArrayScalar, which uses deserialize_any, to handle both bytes and seq
let bytes: Box<[u8; 32]> = map.next_value_seed(BytesOrArrayScalar)?;
match curve_str.as_str() {
"K256" => Ok(EccScalarBytes::K256(bytes)),
"P256" => Ok(EccScalarBytes::P256(bytes)),
"Ed25519" => Ok(EccScalarBytes::Ed25519(bytes)),
other => Err(de::Error::unknown_variant(
other,
&["K256", "P256", "Ed25519"],
)),
}
}
}

deserializer.deserialize_map(EccScalarBytesVisitor)
}
}

impl EccScalarBytes {
pub fn curve_type(&self) -> EccCurveType {
match self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -692,3 +692,51 @@ fn test_scalar_inversion() -> CanisterThresholdResult<()> {
}
Ok(())
}

#[test]
fn test_scalarbytes_deserialization_with_old_cbor_format() {
let old_ser = [
(EccCurveType::K256, hex!("a1644b323536982018c8183c18431893183f188f188e182e1857181b18ab18ce041870187f183b1856185d1886187018d518ce182418421871184618ba187b186918ac09183d").to_vec()),
(EccCurveType::P256, hex!("a1645032353698201830182a185d1018c018290a189e189b04183c1822185b061896182e187018af1887182f18d1182e183b189b18cb18441822187618b303185c186f").to_vec()),
(EccCurveType::Ed25519, hex!("a16745643235353139982018a118a9187c11187818ed18df18cb182218ad184b18ce182b187f1878188618d9185d18d9186d11181f182b1892186018221882187a18a8186f0c0e").to_vec()),
];

for (curve, bytes) in &old_ser {
let sb: EccScalarBytes =
serde_cbor::from_slice(bytes).expect("Failed to deserialize CBOR encoding");

assert_eq!(*curve, sb.curve_type());

// Confirm that (for now) the default serialization is the old format for K256 and Ed25519
if *curve == EccCurveType::K256 || *curve == EccCurveType::Ed25519 {
assert_eq!(
hex::encode(serde_cbor::to_vec(&sb).unwrap()),
hex::encode(bytes)
);
}
}
}

#[test]
fn test_scalarbytes_deserialization_compact_cbor_format() {
let compact_ser = [
(EccCurveType::K256, hex!("a1644b3235365820b5ebe6143c8a7f7f459413a69c34ffce7227ea0e37f3524e67283b1e99fd8194").to_vec()),
(EccCurveType::P256, hex!("a164503235365820eadb7e4360365ce1a417d9fdddda706296e367053e14136f57b4a69b00494c06").to_vec()),
(EccCurveType::Ed25519, hex!("a1674564323535313958207f7096a7e536695c1ecce3d6b3ba75e81bd910a79cb2e33f1e60cc4df292e404").to_vec()),
];

for (curve, bytes) in &compact_ser {
let sb: EccScalarBytes =
serde_cbor::from_slice(bytes).expect("Failed to deserialize CBOR encoding");

assert_eq!(*curve, sb.curve_type());

// Confirm that the new format is used for P256
if *curve == EccCurveType::P256 {
assert_eq!(
hex::encode(serde_cbor::to_vec(&sb).unwrap()),
hex::encode(bytes)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -798,14 +798,14 @@ fn commitment_opening_p256_serialization_is_stable() -> Result<(), CanisterThres

assert_eq!(
hex::encode(serde_cbor::to_vec(&simple).unwrap()),
"a16653696d706c65a1645032353698201853183d18b717183618db18b1181c182318fd189a186c18d70318d3187a18fd1851187318b9184318dc1893182d1838187d18c1187c188918aa18ad1884"
"a16653696d706c65a164503235365820533db71736dbb11c23fd9a6cd703d37afd5173b943dc932d387dc17c89aaad84",
);

let pedersen = CommitmentOpeningBytes::Pedersen(s1_bytes, s2_bytes);

assert_eq!(
hex::encode(serde_cbor::to_vec(&pedersen).unwrap()),
"a168506564657273656e82a1645032353698201853183d18b717183618db18b1181c182318fd189a186c18d70318d3187a18fd1851187318b9184318dc1893182d1838187d18c1187c188918aa18ad1884a1645032353698201843181f18b6141845184b187c181f182e18c218bd18761883182d18af184e18c618ca18da18a3188b18fb18fb1880181a186d1820189b1827185a18f2188d"
"a168506564657273656e82a164503235365820533db71736dbb11c23fd9a6cd703d37afd5173b943dc932d387dc17c89aaad84a164503235365820431fb614454b7c1f2ec2bd76832daf4ec6cadaa38bfbfb801a6d209b275af28d"
);

Ok(())
Expand Down
Loading