Skip to content

Commit b8095e1

Browse files
committed
zklogin: check address_seed is at most 32 bytes long
1 parent 5e88c33 commit b8095e1

File tree

6 files changed

+275
-27
lines changed

6 files changed

+275
-27
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ base64ct = { version = "1.6.0", features = ["alloc"] }
3232
bs58 = "0.5.0"
3333
hex = "0.4.3"
3434
roaring = { version = "0.10.3", default-features = false }
35+
bnum = "0.10.0"
3536

3637
# Serialization and Deserialization support
3738
serde = { version = "1.0.197", optional = true }
@@ -48,6 +49,7 @@ blake2 = { version = "0.10.6", optional = true }
4849
[dev-dependencies]
4950
bcs = "0.1.6"
5051
serde_json = "1.0.114"
52+
num-bigint = "0.4.4"
5153

5254
# proptest support in tests
5355
#

src/types/crypto/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub use secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey, Secp256k1Signature}
1616
pub use secp256r1::{Secp256r1PrivateKey, Secp256r1PublicKey, Secp256r1Signature};
1717
pub use signature::{SignatureScheme, SimpleSignature, UserSignature};
1818
pub use zklogin::{
19-
Claim, Jwk, JwkId, JwtDetails, ZkLoginAuthenticator, ZkLoginInputs, ZkLoginProof,
19+
AddressSeed, Claim, Jwk, JwkId, JwtDetails, ZkLoginAuthenticator, ZkLoginInputs, ZkLoginProof,
2020
ZkLoginPublicIdentifier,
2121
};
2222

src/types/crypto/multisig.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ fn roaring_bitmap_to_u16(roaring: &roaring::RoaringBitmap) -> Result<BitmapUnit,
126126

127127
#[derive(Debug, Clone, PartialEq, Eq)]
128128
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
129+
#[allow(clippy::large_enum_variant)]
129130
pub enum MultisigMemberSignature {
130131
Ed25519(Ed25519Signature),
131132
Secp256k1(Secp256k1Signature),
@@ -589,6 +590,7 @@ mod serialization {
589590
}
590591

591592
#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
593+
#[allow(clippy::large_enum_variant)]
592594
enum MemberSignature {
593595
Ed25519(Ed25519Signature),
594596
Secp256k1(Secp256k1Signature),
@@ -598,6 +600,7 @@ mod serialization {
598600

599601
#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
600602
#[serde(tag = "scheme", rename_all = "lowercase")]
603+
#[allow(clippy::large_enum_variant)]
601604
enum ReadableMemberSignature {
602605
Ed25519 { signature: Ed25519Signature },
603606
Secp256k1 { signature: Secp256k1Signature },

src/types/crypto/zklogin.rs

Lines changed: 131 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::SimpleSignature;
2-
use crate::types::checkpoint::EpochId;
2+
use crate::types::{checkpoint::EpochId, u256::U256};
33

44
/// An zk login authenticator with all the necessary fields.
55
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -19,7 +19,7 @@ pub struct ZkLoginInputs {
1919
proof_points: ZkLoginProof,
2020
iss_base64_details: Claim,
2121
header_base64: String,
22-
address_seed: String,
22+
address_seed: AddressSeed,
2323
// #[serde(skip)]
2424
// jwt_details: JwtDetails,
2525
}
@@ -73,8 +73,7 @@ pub type CircomG2 = Vec<Vec<String>>;
7373
#[derive(Clone, Debug, PartialEq, Eq)]
7474
pub struct ZkLoginPublicIdentifier {
7575
iss: String,
76-
//TODO bigint support
77-
address_seed: [u8; 32],
76+
address_seed: AddressSeed,
7877
}
7978

8079
/// Struct that contains info for a JWK. A list of them for different kids can
@@ -109,6 +108,107 @@ pub struct JwkId {
109108
pub kid: String,
110109
}
111110

111+
#[derive(Clone, Debug, PartialEq, Eq)]
112+
pub struct AddressSeed([u8; 32]);
113+
114+
impl AddressSeed {
115+
pub fn unpadded(&self) -> &[u8] {
116+
let mut buf = self.0.as_slice();
117+
118+
while !buf.is_empty() && buf[0] == 0 {
119+
buf = &buf[1..];
120+
}
121+
122+
// If the value is '0' then just return a slice of length 1 of the final byte
123+
if buf.is_empty() {
124+
&self.0[31..]
125+
} else {
126+
buf
127+
}
128+
}
129+
130+
pub fn padded(&self) -> &[u8] {
131+
&self.0
132+
}
133+
}
134+
135+
impl std::fmt::Display for AddressSeed {
136+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137+
let u256 = U256::from_be(U256::from_digits(self.0));
138+
let radix10 = u256.to_str_radix(10);
139+
f.write_str(&radix10)
140+
}
141+
}
142+
143+
#[derive(Debug)]
144+
pub struct AddressSeedParseError(bnum::errors::ParseIntError);
145+
146+
impl std::fmt::Display for AddressSeedParseError {
147+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148+
write!(f, "unable to parse radix10 encoded value {}", self.0)
149+
}
150+
}
151+
152+
impl std::error::Error for AddressSeedParseError {}
153+
154+
impl std::str::FromStr for AddressSeed {
155+
type Err = AddressSeedParseError;
156+
157+
fn from_str(s: &str) -> Result<Self, Self::Err> {
158+
let u256 = U256::from_str_radix(s, 10).map_err(AddressSeedParseError)?;
159+
let be = u256.to_be();
160+
Ok(Self(*be.digits()))
161+
}
162+
}
163+
164+
#[cfg(test)]
165+
mod test {
166+
use super::AddressSeed;
167+
use num_bigint::BigUint;
168+
use proptest::prelude::*;
169+
use std::str::FromStr;
170+
171+
#[cfg(target_arch = "wasm32")]
172+
use wasm_bindgen_test::wasm_bindgen_test as test;
173+
174+
#[test]
175+
fn unpadded_slice() {
176+
let seed = AddressSeed([0; 32]);
177+
let zero: [u8; 1] = [0];
178+
assert_eq!(seed.unpadded(), zero.as_slice());
179+
180+
let mut seed = AddressSeed([1; 32]);
181+
seed.0[0] = 0;
182+
assert_eq!(seed.unpadded(), [1; 31].as_slice());
183+
}
184+
185+
proptest! {
186+
#[test]
187+
fn dont_crash_on_large_inputs(
188+
bytes in proptest::collection::vec(any::<u8>(), 33..1024)
189+
) {
190+
let big_int = BigUint::from_bytes_be(&bytes);
191+
let radix10 = big_int.to_str_radix(10);
192+
193+
// doesn't crash
194+
let _ = AddressSeed::from_str(&radix10);
195+
}
196+
197+
#[test]
198+
fn valid_address_seeds(
199+
bytes in proptest::collection::vec(any::<u8>(), 1..=32)
200+
) {
201+
let big_int = BigUint::from_bytes_be(&bytes);
202+
let radix10 = big_int.to_str_radix(10);
203+
204+
let seed = AddressSeed::from_str(&radix10).unwrap();
205+
assert_eq!(radix10, seed.to_string());
206+
// Ensure unpadded doesn't crash
207+
seed.unpadded();
208+
}
209+
}
210+
}
211+
112212
#[cfg(feature = "serde")]
113213
#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
114214
mod serialization {
@@ -121,6 +221,7 @@ mod serialization {
121221
use serde::Serializer;
122222
use serde_with::Bytes;
123223
use serde_with::DeserializeAs;
224+
use serde_with::SerializeAs;
124225
use std::borrow::Cow;
125226

126227
// Serialized format is: iss_bytes_len || iss_bytes || padded_32_byte_address_seed.
@@ -133,16 +234,11 @@ mod serialization {
133234
#[derive(serde_derive::Serialize)]
134235
struct Readable<'a> {
135236
iss: &'a str,
136-
//TODO this needs to be encoded as a Decimal u256 instead of in base64
137-
#[cfg_attr(
138-
feature = "serde",
139-
serde(with = "::serde_with::As::<crate::types::crypto::Base64Array32>")
140-
)]
141-
address_seed: [u8; 32],
237+
address_seed: &'a AddressSeed,
142238
}
143239
let readable = Readable {
144240
iss: &self.iss,
145-
address_seed: self.address_seed,
241+
address_seed: &self.address_seed,
146242
};
147243
readable.serialize(serializer)
148244
} else {
@@ -151,7 +247,7 @@ mod serialization {
151247
buf.push(iss_bytes.len() as u8);
152248
buf.extend(iss_bytes);
153249

154-
buf.extend(&self.address_seed);
250+
buf.extend(&self.address_seed.0);
155251

156252
serializer.serialize_bytes(&buf)
157253
}
@@ -167,12 +263,7 @@ mod serialization {
167263
#[derive(serde_derive::Deserialize)]
168264
struct Readable {
169265
iss: String,
170-
//TODO this needs to be encoded as a Decimal u256 instead of in base64
171-
#[cfg_attr(
172-
feature = "serde",
173-
serde(with = "::serde_with::As::<crate::types::crypto::Base64Array32>")
174-
)]
175-
address_seed: [u8; 32],
266+
address_seed: AddressSeed,
176267
}
177268

178269
let Readable { iss, address_seed } = Deserialize::deserialize(deserializer)?;
@@ -188,8 +279,9 @@ mod serialization {
188279
.get((1 + iss_len as usize)..)
189280
.ok_or_else(|| serde::de::Error::custom("invalid zklogin public identifier"))?;
190281

191-
let address_seed =
192-
<[u8; 32]>::try_from(address_seed_bytes).map_err(serde::de::Error::custom)?;
282+
let address_seed = <[u8; 32]>::try_from(address_seed_bytes)
283+
.map_err(serde::de::Error::custom)
284+
.map(AddressSeed)?;
193285

194286
Ok(Self {
195287
iss: iss.into(),
@@ -282,4 +374,23 @@ mod serialization {
282374
})
283375
}
284376
}
377+
378+
// AddressSeed's serialized format is as a radix10 encoded string
379+
impl Serialize for AddressSeed {
380+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
381+
where
382+
S: serde::Serializer,
383+
{
384+
serde_with::DisplayFromStr::serialize_as(self, serializer)
385+
}
386+
}
387+
388+
impl<'de> Deserialize<'de> for AddressSeed {
389+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
390+
where
391+
D: Deserializer<'de>,
392+
{
393+
serde_with::DisplayFromStr::deserialize_as(deserializer)
394+
}
395+
}
285396
}

src/types/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ mod crypto;
44
mod digest;
55
mod gas;
66
mod object_id;
7+
mod u256;
78

89
pub use address::Address;
910
pub use checkpoint::{CheckpointCommitment, CheckpointSummary, EndOfEpochData};
1011
pub use crypto::{
11-
Bls12381PrivateKey, Bls12381PublicKey, Bls12381Signature, Claim, Ed25519PrivateKey,
12-
Ed25519PublicKey, Ed25519Signature, Jwk, JwkId, JwtDetails, MultisigAggregatedSignature,
13-
MultisigCommittee, MultisigMember, MultisigMemberPublicKey, MultisigMemberSignature,
14-
Secp256k1PrivateKey, Secp256k1PublicKey, Secp256k1Signature, Secp256r1PrivateKey,
15-
Secp256r1PublicKey, Secp256r1Signature, SignatureScheme, SimpleSignature, UserSignature,
16-
ZkLoginAuthenticator, ZkLoginInputs, ZkLoginProof, ZkLoginPublicIdentifier,
12+
AddressSeed, Bls12381PrivateKey, Bls12381PublicKey, Bls12381Signature, Claim,
13+
Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature, Jwk, JwkId, JwtDetails,
14+
MultisigAggregatedSignature, MultisigCommittee, MultisigMember, MultisigMemberPublicKey,
15+
MultisigMemberSignature, Secp256k1PrivateKey, Secp256k1PublicKey, Secp256k1Signature,
16+
Secp256r1PrivateKey, Secp256r1PublicKey, Secp256r1Signature, SignatureScheme, SimpleSignature,
17+
UserSignature, ZkLoginAuthenticator, ZkLoginInputs, ZkLoginProof, ZkLoginPublicIdentifier,
1718
};
1819
pub use digest::{
1920
CheckpointContentsDigest, CheckpointDigest, Digest, DigestParseError, TransactionDigest,

0 commit comments

Comments
 (0)