diff --git a/Cargo.lock b/Cargo.lock index 5a334d05ad..e520e60b33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,7 +232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -245,7 +245,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -338,7 +338,7 @@ checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -659,7 +659,7 @@ checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -668,6 +668,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cast" version = "0.3.0" @@ -799,7 +805,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -893,6 +899,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1207,7 +1223,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -1224,7 +1240,7 @@ checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1248,7 +1264,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -1259,7 +1275,7 @@ checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1292,7 +1308,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1305,7 +1321,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -1433,6 +1449,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -1450,7 +1475,7 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1471,7 +1496,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1609,7 +1634,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1646,6 +1671,7 @@ dependencies = [ "ark-relations", "ark-serialize", "ark-std", + "bcs", "blake2", "blst", "byte-slice-cast", @@ -1658,9 +1684,11 @@ dependencies = [ "poseidon-ark", "proptest", "regex", + "reqwest", "schemars", "serde", "serde_json", + "tokio", ] [[package]] @@ -1704,6 +1732,69 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1782,6 +1873,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" @@ -1875,6 +1985,77 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1905,6 +2086,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1951,6 +2142,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + [[package]] name = "is-terminal" version = "0.4.4" @@ -2088,7 +2285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2145,6 +2342,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -2154,12 +2357,42 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "more-asserts" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num" version = "0.4.0" @@ -2304,6 +2537,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -2352,12 +2629,24 @@ dependencies = [ "base64ct", ] +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs1" version = "0.4.1" @@ -2390,6 +2679,12 @@ dependencies = [ "spki 0.7.1", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "plotters" version = "0.3.4" @@ -2492,7 +2787,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -2554,7 +2849,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2657,7 +2952,7 @@ checksum = "c75a383c2bcaa4139436e9826a2e606fac946406cf635f5b2363b86f95c3b756" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2730,6 +3025,43 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -2773,7 +3105,7 @@ checksum = "ff26ed6c7c4dfc2aa9480b86a60e3c7233543a270a680e10758a507c5a4ce476" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2880,6 +3212,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "schemars" version = "0.8.12" @@ -2901,7 +3242,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", ] [[package]] @@ -2970,6 +3311,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -3032,7 +3396,7 @@ checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3043,7 +3407,7 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3057,6 +3421,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "2.3.1" @@ -3082,7 +3458,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3166,12 +3542,31 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -3239,6 +3634,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -3247,7 +3653,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -3317,7 +3723,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3376,6 +3782,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.26.0" @@ -3383,7 +3804,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", + "bytes", + "libc", + "memchr", + "mio", "pin-project-lite", + "socket2", "tokio-macros", "windows-sys 0.45.0", ] @@ -3396,9 +3822,39 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -3420,7 +3876,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3463,6 +3919,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "twox-hash" version = "1.6.3" @@ -3486,12 +3948,27 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.10.1" @@ -3520,12 +3997,29 @@ dependencies = [ "subtle", ] +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -3551,6 +4045,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3578,10 +4081,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -3600,7 +4115,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3707,7 +4222,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3949,12 +4464,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] @@ -3964,7 +4479,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.2", ] [[package]] @@ -3973,21 +4497,42 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +dependencies = [ + "windows_aarch64_gnullvm 0.48.2", + "windows_aarch64_msvc 0.48.2", + "windows_i686_gnu 0.48.2", + "windows_i686_msvc 0.48.2", + "windows_x86_64_gnu 0.48.2", + "windows_x86_64_gnullvm 0.48.2", + "windows_x86_64_msvc 0.48.2", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" + [[package]] name = "windows_aarch64_msvc" version = "0.33.0" @@ -4000,6 +4545,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" + [[package]] name = "windows_i686_gnu" version = "0.33.0" @@ -4012,6 +4563,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" + [[package]] name = "windows_i686_msvc" version = "0.33.0" @@ -4024,6 +4581,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" + [[package]] name = "windows_x86_64_gnu" version = "0.33.0" @@ -4036,12 +4599,24 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" + [[package]] name = "windows_x86_64_msvc" version = "0.33.0" @@ -4054,6 +4629,21 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "wycheproof" version = "0.5.0" @@ -4083,6 +4673,6 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] diff --git a/fastcrypto-zkp/Cargo.toml b/fastcrypto-zkp/Cargo.toml index 880389dc59..ef7f47b52f 100644 --- a/fastcrypto-zkp/Cargo.toml +++ b/fastcrypto-zkp/Cargo.toml @@ -32,6 +32,8 @@ serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" once_cell = "1.16" poseidon-ark = { git = "https://github.com/MystenLabs/poseidon-ark.git", rev = "39844046ded0c4aa7a247e8545e131b59a330a9c" } +reqwest = "0.11.18" +bcs = "0.1.4" [dev-dependencies] ark-bls12-377 = "0.4.0" @@ -45,3 +47,4 @@ criterion = "0.4.0" hex = "0.4.3" proptest = "1.1.0" num-bigint = { version = "0.4", default-features = false, features = ["rand"] } +tokio = { version = "1.24.1", features = ["sync", "rt", "macros"] } diff --git a/fastcrypto-zkp/src/bn254/mod.rs b/fastcrypto-zkp/src/bn254/mod.rs index 089a981b3d..66f23647be 100644 --- a/fastcrypto-zkp/src/bn254/mod.rs +++ b/fastcrypto-zkp/src/bn254/mod.rs @@ -18,6 +18,11 @@ pub mod poseidon; /// Zk login structs and utilities pub mod zk_login; +/// Zk login entrypoints +pub mod zk_login_api; + +/// Zk login utils +pub mod utils; /// A field element in the BN254 construction. Thin wrapper around `api::Bn254Fr`. #[derive(Debug, From)] pub struct FieldElement(pub(crate) api::Bn254Fr); diff --git a/fastcrypto-zkp/src/bn254/poseidon.rs b/fastcrypto-zkp/src/bn254/poseidon.rs index 1c4c718151..c0cee6bdac 100644 --- a/fastcrypto-zkp/src/bn254/poseidon.rs +++ b/fastcrypto-zkp/src/bn254/poseidon.rs @@ -38,11 +38,34 @@ impl PoseidonWrapper { .map_err(|_| FastCryptoError::InvalidInput) } } + +/// Calculate the poseidon hash of the field element inputs. If the input +/// length is <= 16, calculate H(inputs), if it is <= 32, calculate H(H(inputs[0..16]), H(inputs[16..32])), otherwise return an error. +pub fn to_poseidon_hash(inputs: Vec) -> Result { + if inputs.len() <= 16 { + let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); + poseidon1.hash(inputs) + } else if inputs.len() <= 32 { + let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); + let hash1 = poseidon1.hash(inputs[0..16].to_vec())?; + + let mut poseidon2 = PoseidonWrapper::new(); + let hash2 = poseidon2.hash(inputs[16..].to_vec())?; + + let mut poseidon3 = PoseidonWrapper::new(); + poseidon3.hash([hash1, hash2].to_vec()) + } else { + Err(FastCryptoError::GeneralError(format!( + "Yet to implement: Unable to hash a vector of length {}", + inputs.len() + ))) + } +} + #[cfg(test)] mod test { use super::PoseidonWrapper; - use crate::bn254::zk_login::Bn254Fr; - use crate::bn254::zk_login::{calculate_merklized_hash, to_poseidon_hash}; + use crate::bn254::{poseidon::to_poseidon_hash, zk_login::Bn254Fr}; use ark_bn254::Fr; use std::str::FromStr; @@ -54,13 +77,6 @@ mod test { #[test] fn poseidon_test() { - // TODO (joyqvq): add more test vectors here from circom.js - // Test vector generated from circom.js - // Poseidon([134696963602902907403122104327765350261n, - // 17932473587154777519561053972421347139n, - // 10000, - // 50683480294434968413708503290439057629605340925620961559740848568164438166n]) - // = 2272550810841985018139126931041192927190568084082399473943239080305281957330n let mut poseidon = PoseidonWrapper::new(); let input1 = Fr::from_str("134696963602902907403122104327765350261").unwrap(); let input2 = Fr::from_str("17932473587154777519561053972421347139").unwrap(); @@ -79,43 +95,50 @@ mod test { ); } #[test] - fn test_merklized_hash() { - let masked_content = b"eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ.=yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC===========================================================================================================CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC==========================================================================================================================================================================================================================================================================================================\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + fn test_to_poseidon_hash() { assert_eq!( - calculate_merklized_hash(masked_content).unwrap(), - "14900420995580824499222150327925943524564997104405553289134597516335134742309" - ); - - assert_eq!( - to_poseidon_hash(to_bigint_arr(vec![1])).unwrap(), + to_poseidon_hash(to_bigint_arr(vec![1])) + .unwrap() + .to_string(), "18586133768512220936620570745912940619677854269274689475585506675881198879027" ); assert_eq!( - to_poseidon_hash(to_bigint_arr(vec![1, 2])).unwrap(), + to_poseidon_hash(to_bigint_arr(vec![1, 2])) + .unwrap() + .to_string(), "7853200120776062878684798364095072458815029376092732009249414926327459813530" ); assert_eq!( to_poseidon_hash(to_bigint_arr(vec![ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ])) - .unwrap(), + .unwrap() + .to_string(), "4203130618016961831408770638653325366880478848856764494148034853759773445968" ); assert_eq!( to_poseidon_hash(to_bigint_arr(vec![ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ])) - .unwrap(), - "13895998335546007571506436905298853781676311844723695580596383169075721618652" + .unwrap() + .to_string(), + "9989051620750914585850546081941653841776809718687451684622678807385399211877" ); assert_eq!( to_poseidon_hash(to_bigint_arr(vec![ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 ])) - .unwrap(), - "14023706212980258922092162104379517008998397500440232747089120702484714603058" + .unwrap() + .to_string(), + "4123755143677678663754455867798672266093104048057302051129414708339780424023" ); + + assert!(to_poseidon_hash(to_bigint_arr(vec![ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32 + ])) + .is_err()); } #[test] diff --git a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs index 934f42161a..e40d242591 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,64 +1,429 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; +use std::str::FromStr; + +use crate::bn254::utils::get_nonce; use crate::bn254::zk_login::{ - verify_zk_login_proof_with_fixed_vk, AuxInputs, OAuthProvider, PublicInputs, ZkLoginProof, + decode_base64_url, hash_ascii_str_to_field, hash_to_field, parse_jwks, trim, + verify_extended_claim, Claim, JWTDetails, JWTHeader, +}; +use crate::bn254::zk_login::{fetch_jwks, OIDCProvider}; +use crate::bn254::zk_login_api::Environment; +use crate::bn254::{ + zk_login::{ZkLoginInputs, JWK}, + zk_login_api::verify_zk_login, }; +use ark_std::rand::rngs::StdRng; +use ark_std::rand::SeedableRng; +use fastcrypto::ed25519::Ed25519KeyPair; +use fastcrypto::error::FastCryptoError; +use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; +use fastcrypto::traits::KeyPair; +use num_bigint::BigUint; -use super::ParsedMaskedContent; +const GOOGLE_JWK_BYTES: &[u8] = r#"{ + "keys": [ + { + "n": "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw==", + "use": "sig", + "alg": "RS256", + "e": "AQAB", + "kid": "911e39e27928ae9f1e9d1e21646de92d19351b44", + "kty": "RSA" + }, + { + "n": "pGMz603XOzO71r-LpW555Etbn2dXAtY4xToNE_Upr1EHxkHFnVnGPsbOeWzP8xU1IpAL56S3sTsbpCR_Ci_PYq8s4I3VWQM0u9w1D_e45S1KJTSex_aiMQ_cjTXb3Iekc00JIkMJhUaNnbsEt7PlOmnyFqvN-G3ZXVDfTuL2Wsn4tRMYf7YU3jgTVN2M_p7bcZYHhkEB-jzNeK7ub-6mOMkKdYWnk0jIoRfV63d32bub0pQpWv8sVmflgK2xKUSJVMZ7CM0FvJYJgF7y42KBPYc6Gm_UWE0uHazDgZgAvQQoNyEF_TRjVfGiihjPFYCPqvFcfLK4773JTD2fLZTgOQ==", + "kid": "7c9c78e3b00e1bb092d246c887b11220c87b7d20", + "e": "AQAB", + "alg": "RS256", + "kty": "RSA", + "use": "sig" + }, + { + "use": "sig", + "kid": "fd48a75138d9d48f0aa635ef569c4e196f7ae8d6", + "e": "AQAB", + "n": "8KImylelEspnZ0X-ekZb9VPbUFhgB_yEPJuLKOhXOWJLVsU0hJP6B_mQOfVk0CHm66UsAhqV8qrINk-RXgwVaaFLMA827pbOOBhyvHsThcyo7AY5s6M7qbftFKKnkfVHO6c9TsQ9wpIfmhCVL3QgTlqlgFQWcNsY-qemSKpqvVi-We9I3kPvbTf0PKJ_rWA7GQQnU_GA5JRU46uvw4I1ODf0icNBHw7pWc7oTvmSl1G8OWABEyiFakcUG2Xd4qZnmWaKwLHBvifPuIyy2vK-yHH91mVZCuleVu53Vzj77RgUtF2EEuB-zizwC-fzaBmvnfx1kgQLsdK22J0Ivgu4Xw==", + "kty": "RSA", + "alg": "RS256" + } + ] + }"#.as_bytes(); + +const TWITCH_JWK_BYTES: &[u8] = r#"{ + "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"sig"}] + }"#.as_bytes(); + +const FACEBOOK_JWK_BYTES: &[u8] = r#"{ + "keys": [ + { + "kid": "5931701331165f07f550ac5f0194942d54f9c249", + "kty": "RSA", + "alg": "RS256", + "use": "sig", + "n": "-GuAIboTsRYNprJQOkdmuKXRx8ARnKXOC9Pajg4KxHHPt3OY8rXRmVeDxTj1-m9TfW6V-wJa_8ncBbbFE-aV-eBi_XeuIToBBvLZp1-UPIjitS8WCDrUhHiJnbvkIZf1B1YBIq_Ua81fzxhtjQ0jDftV2m5aavmJG4_94VG3Md7noQjjUKzxJyUNl4v_joMA6pIRCeeamvfIZorjcR4wVf-wR8NiZjjRbcjKBpc7ztc7Gm778h34RSe9-DLH6uicTROSYNa99pUwhn3XVfAv4hTFpLIcgHYadLZjsHfUvivr76uiYbxDZx6UTkK5jmi51b87u1b6iYmijDIMztzrIQ", + "e": "AQAB" + }, + { + "kid": "a378585d826a933cc207ce31cad63c019a53095c", + "kty": "RSA", + "alg": "RS256", + "use": "sig", + "n": "1aLDAmRq-QeOr1b8WbtpmD5D4CpE5S0YrNklM5BrRjuZ6FTG8AhqvyUUnAb7Dd1gCZgARbuk2yHOOca78JWX2ocAId9R4OV2PUoIYljDZ5gQJBaL6liMpolQjlqovmd7IpF8XZWudWU6Rfhoh-j6dd-8IHeJjBKMYij0CuA6HZ1L98vBW1ehEdnBZPfTe28H57hySzucnC1q1340h2E2cpCfLZ-vNoYQ4Qe-CZKpUAKOoOlC4tWCt2rLcsV_vXvmNlLv_UYGbJEFKS-I1tEwtlD71bvn9WWluE7L4pWlIolgzNyIz4yxe7G7V4jlvSSwsu1ZtIQzt5AtTC--5HEAyQ", + "e": "AQAB" + } + ] + }"#.as_bytes(); + +const BAD_JWK_BYTES: &[u8] = r#"{ + "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"wrong usage"}] + }"#.as_bytes(); #[test] -fn test_verify_groth16_in_bytes_api() { - let aux_inputs = AuxInputs::from_json("{\"addr_seed\":\"15604334753912523265015800787270404628529489918817818174033741053550755333691\",\"eph_public_key\":[\"17932473587154777519561053972421347139\",\"134696963602902907403122104327765350261\"],\"jwt_sha2_hash\":[\"248987002057371616691124650904415756047\",\"113498781424543581252500776698433499823\"],\"jwt_signature\":\"\",\"key_claim_name\":\"sub\",\"masked_content\":[101,121,74,104,98,71,99,105,79,105,74,83,85,122,73,49,78,105,73,115,73,109,116,112,90,67,73,54,73,109,77,53,89,87,90,107,89,84,77,50,79,68,74,108,89,109,89,119,79,87,86,105,77,122,65,49,78,87,77,120,89,122,82,105,90,68,77,53,89,106,99,49,77,87,90,105,90,106,103,120,79,84,85,105,76,67,74,48,101,88,65,105,79,105,74,75,86,49,81,105,102,81,46,61,121,74,112,99,51,77,105,79,105,74,111,100,72,82,119,99,122,111,118,76,50,70,106,89,50,57,49,98,110,82,122,76,109,100,118,98,50,100,115,90,83,53,106,98,50,48,105,76,67,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,67,74,104,100,87,81,105,79,105,73,49,78,122,85,49,77,84,107,121,77,68,81,121,77,122,99,116,98,88,78,118,99,68,108,108,99,68,81,49,100,84,74,49,98,122,107,52,97,71,70,119,99,87,49,117,90,51,89,52,90,68,103,48,99,87,82,106,79,71,115,117,89,88,66,119,99,121,53,110,98,50,57,110,98,71,86,49,99,50,86,121,89,50,57,117,100,71,86,117,100,67,53,106,98,50,48,105,76,67,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"max_epoch\":10000,\"num_sha2_blocks\":11,\"payload_len\":564,\"payload_start_index\":103}").unwrap(); - let public_inputs = PublicInputs::from_json( - "[\"2487117669597822357956926047501254969190518860900347921480370492048882803688\"]", - ) - .unwrap(); +fn test_verify_zk_login_google() { + use crate::bn254::zk_login_api::Bn254Fr; + use std::str::FromStr; + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pubkey = vec![0x00]; + eph_pubkey.extend(kp.public().as_ref()); + + assert!(ZkLoginInputs::from_json("{\"something\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").is_err()); + + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"19787893228347416264175863455553559620428641614896993159007341506168718628112\",\"9525429549286657723959098685731622393483997723182834572003815901856723580461\",\"1\"],\"pi_b\":[[\"3493347571535213714356485688393536339935225306901617492672730508554674104240\",\"16985532751431776840148620485065973908539904507322892849232044565343489766128\"],[\"13893319072314620992397619679873669034526557780388381368678976150917704243179\",\"640742494801049238290284649145440140989242166770354858908399041461288677398\"],[\"1\",\"0\"]],\"pi_c\":[\"21862480547331092832339784749917351442633352096270524226556828302437894726278\",\"12611981741824395163083120319401608319346495925967864800017660949138781692880\",\"1\"]},\"address_seed\":\"15909817818955140159551330044992054478592339431921061309213971300613403932293\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdjOWM3OGUzYjAwZTFiYjA5MmQyNDZjODg3YjExMjIwYzg3YjdkMjAiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); + assert_eq!( + zklogin_inputs.get_kid(), + "7c9c78e3b00e1bb092d246c887b11220c87b7d20".to_string() + ); + assert_eq!( + zklogin_inputs.get_iss(), + OIDCProvider::Google.get_config().0.to_string() + ); assert_eq!( - aux_inputs.calculate_all_inputs_hash().unwrap(), - public_inputs.get_all_inputs_hash() + zklogin_inputs.get_aud(), + "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com".to_string() ); + assert_eq!( + zklogin_inputs.get_address_params().aud, + "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com".to_string() + ); + assert_eq!( + zklogin_inputs.get_address_params().iss, + OIDCProvider::Google.get_config().0.to_string() + ); + assert_eq!( + zklogin_inputs.get_address_seed(), + "15909817818955140159551330044992054478592339431921061309213971300613403932293" + ); + let mut map = HashMap::new(); + let content = JWK { + kty: "RSA".to_string(), + e: "AQAB".to_string(), + n: "pGMz603XOzO71r-LpW555Etbn2dXAtY4xToNE_Upr1EHxkHFnVnGPsbOeWzP8xU1IpAL56S3sTsbpCR_Ci_PYq8s4I3VWQM0u9w1D_e45S1KJTSex_aiMQ_cjTXb3Iekc00JIkMJhUaNnbsEt7PlOmnyFqvN-G3ZXVDfTuL2Wsn4tRMYf7YU3jgTVN2M_p7bcZYHhkEB-jzNeK7ub-6mOMkKdYWnk0jIoRfV63d32bub0pQpWv8sVmflgK2xKUSJVMZ7CM0FvJYJgF7y42KBPYc6Gm_UWE0uHazDgZgAvQQoNyEF_TRjVfGiihjPFYCPqvFcfLK4773JTD2fLZTgOQ".to_string(), + alg: "RS256".to_string(), + }; + map.insert( + ( + "7c9c78e3b00e1bb092d246c887b11220c87b7d20".to_string(), + OIDCProvider::Google.get_config().0.to_string(), + ), + content.clone(), + ); + let modulus = Base64UrlUnpadded::decode_vec(&content.n).unwrap(); assert_eq!( - aux_inputs.get_jwt_hash(), - vec![ - 187, 81, 38, 253, 76, 198, 157, 166, 214, 87, 161, 53, 77, 141, 223, 15, 85, 99, 17, - 247, 75, 248, 40, 150, 239, 21, 140, 190, 12, 123, 242, 175 - ] + zklogin_inputs + .calculate_all_inputs_hash(&eph_pubkey, &modulus, 10) + .unwrap(), + vec![Bn254Fr::from_str( + "9496323448584064558296338231676268184078052204097371080436367115432777673272" + ) + .unwrap()] ); + let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map, Environment::Test); + assert!(res.is_ok()); +} + +#[test] +fn test_verify_zk_login_twitch() { + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pubkey = vec![0x00]; + eph_pubkey.extend(kp.public().as_ref()); + + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"13051843614423432670927014216106415297302637061714876201698143724579485057433\",\"4502868464003469408525038047182455632737577658719986979336848385442638461715\",\"1\"],\"pi_b\":[[\"3766376969677702623227635810414794209012765519614143648451277080679068806865\",\"15274848924865252629859978623879023835428524488301134035498591572116063831502\"],[\"7946138017363758578225489333393267390091071305689481146893911497411367587561\",\"2088365154316551099212086738010689343716611426876233601699514012876088160321\"],[\"1\",\"0\"]],\"pi_c\":[\"5241274211994340733749437701839810664498991243102320350483342489924082173589\",\"15038895168244503564969393433273344359013838973322340723065298413943589759359\",\"1\"]},\"address_seed\":\"15454157267374145582481438333218897413377268576773635507925280300337575904853\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); + assert_eq!(zklogin_inputs.get_kid(), "1".to_string()); assert_eq!( - aux_inputs.get_eph_pub_key(), - vec![ - 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, - 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117 - ] + zklogin_inputs.get_iss(), + OIDCProvider::Twitch.get_config().0.to_string() + ); + assert_eq!( + zklogin_inputs.get_aud(), + "rs1bh065i9ya4ydvifixl4kss0uhpt".to_string() + ); + assert_eq!( + zklogin_inputs.get_address_params().aud, + "rs1bh065i9ya4ydvifixl4kss0uhpt".to_string() + ); + assert_eq!( + zklogin_inputs.get_address_params().iss, + zklogin_inputs.get_iss() + ); + assert_eq!( + zklogin_inputs.get_address_seed(), + "15454157267374145582481438333218897413377268576773635507925280300337575904853" + ); + + let mut map = HashMap::new(); + map.insert(("1".to_string(), OIDCProvider::Twitch.get_config().0.to_string()), JWK { + kty: "RSA".to_string(), + e: "AQAB".to_string(), + n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), + alg: "RS256".to_string(), + }); + let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map, Environment::Test); + assert!(res.is_ok()); +} + +#[test] +fn test_verify_zk_login_facebook() { + // TODO +} + +#[test] +fn test_parsed_masked_content() { + let header = JWTHeader::new("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ").unwrap(); + assert_eq!(header.alg, "RS256"); + assert_eq!(header.typ, "JWT"); + + // Invalid base64 + assert_eq!( + JWTHeader::new("").unwrap_err(), + FastCryptoError::InvalidInput + ); + const VALID_HEADER: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ"; + + // iss not found + assert_eq!( + JWTDetails::new(VALID_HEADER, &[]).unwrap_err(), + FastCryptoError::GeneralError("Invalid claim".to_string()) + ); + + // missing claim + assert_eq!( + JWTDetails::new( + VALID_HEADER, + &[Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }] + ) + .unwrap_err(), + FastCryptoError::GeneralError("Invalid claim".to_string()) + ); + + // unknown claim name + assert_eq!( + JWTDetails::new( + VALID_HEADER, + &[Claim { + name: "unknown".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }] + ) + .unwrap_err(), + FastCryptoError::GeneralError("iss not found in claims".to_string()) + ); + + // bad index_mod_4 + assert_eq!( + JWTDetails::new( + VALID_HEADER, + &[ + Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }, + Claim { + name: "aud".to_string(), + value_base64: "yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC" + .to_string(), + index_mod_4: 2 + } + ] + ) + .unwrap_err(), + FastCryptoError::GeneralError("Invalid UTF8 string".to_string()) + ); + + // first claim is not iss + assert_eq!( + JWTDetails::new( + VALID_HEADER, + &[Claim { + name: "aud".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }] + ) + .unwrap_err(), + FastCryptoError::GeneralError("iss not found in claims".to_string()) + ); + + // second claim is not aud + assert_eq!( + JWTDetails::new( + VALID_HEADER, + &[ + Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }, + Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + } + ] + ) + .unwrap_err(), + FastCryptoError::GeneralError("aud not found in claims".to_string()) + ); +} + +#[test] +fn test_decode_base64() { + assert_eq!( + decode_base64_url("", &0).unwrap_err(), + FastCryptoError::GeneralError("Base64 string smaller than 2".to_string()) + ); + assert_eq!( + decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &0).unwrap_err(), + FastCryptoError::GeneralError("Invalid last_char_offset".to_string()) + ); + assert!(decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &1).is_ok()); + assert_eq!( + decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &2).unwrap_err(), + FastCryptoError::GeneralError("Invalid UTF8 string".to_string()) + ); + assert_eq!( + decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &3).unwrap_err(), + FastCryptoError::GeneralError("Invalid first_char_offset".to_string()) + ); +} + +#[test] +fn test_verify_extended_claim() { + // does not end with , or } + assert_eq!( + verify_extended_claim("\"iss\":\"https://accounts.google.com\"", "iss").unwrap_err(), + FastCryptoError::GeneralError("Invalid extended claim".to_string()) ); - assert_eq!(aux_inputs.get_max_epoch(), 10000); - assert!(aux_inputs.get_jwt_signature().is_ok()); - assert_eq!(aux_inputs.get_iss(), OAuthProvider::Google.get_config().0); - assert_eq!(aux_inputs.get_claim_name(), "sub"); + + // Unexpected claim name + assert_eq!( + verify_extended_claim("\"iss\":\"https://accounts.google.com\",", "aud").unwrap_err(), + FastCryptoError::InvalidInput + ); + + // Malformed json assert_eq!( - aux_inputs.get_client_id(), - "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com" + verify_extended_claim("iss\":\"https://accounts.google.com\"", "iss").unwrap_err(), + FastCryptoError::GeneralError("Invalid extended claim".to_string()) ); + assert_eq!( + verify_extended_claim("\"iss\"\"https://accounts.google.com\"", "iss").unwrap_err(), + FastCryptoError::GeneralError("Invalid extended claim".to_string()) + ); +} - let zk_login_proof = ZkLoginProof::from_json("{\"pi_a\":[\"20070135235140453412363491950139702043798224873934096121884449618027498346650\",\"13452863257899491867230158359348144830940035303347103011373365564048084133173\",\"1\"],\"pi_b\":[[\"20638328149829717497898296893247679667811257514682013496341452050037879873527\",\"14567869016011681044567557818367451228190153931364680049952266175100520394660\"],[\"9918106341458194117820109842171662726217686693538121470076005914489542849473\",\"9007925129766485823687923464528692530984014268682479943901937985832629774609\"],[\"1\",\"0\"]],\"pi_c\":[\"8734022240376125982913134696535916485635821200541495641215567164823074832847\",\"10246591422531428652485093520381188142407035304263437810405902704187483736151\",\"1\"],\"protocol\":\"groth16\"}"); - assert!(zk_login_proof.is_ok()); - let res = verify_zk_login_proof_with_fixed_vk(&zk_login_proof.unwrap(), &public_inputs); - assert!(res.unwrap()); +#[test] +fn test_hash_ascii_str_to_field() { + // Test generated against typescript implementation. + assert_eq!( + hash_ascii_str_to_field("test@gmail.com", 30) + .unwrap() + .to_string(), + "13606676331558803166736332982602687405662978305929711411606106012181987145625" + ); +} + +#[test] +fn test_hash_to_field() { + // Test generated against typescript implementation. + assert_eq!( + hash_to_field( + &[ + BigUint::from_str("32").unwrap(), + BigUint::from_str("25").unwrap(), + BigUint::from_str("73").unwrap() + ], + 8, + 16 + ) + .unwrap() + .to_string(), + "11782828208033177576380997957942702678240059658740659662920410026149313654840".to_string() + ); } #[test] -fn test_masked_content_parse() { - // bytes after 64 * num_sha2_blocks contains non-zeros fails. - let content = ParsedMaskedContent::new(&[1; 65], 0, 0, 1); - assert!(content.is_err()); +fn test_jwk_parse() { + assert_eq!( + trim("wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww==".to_string()), + "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww" + ); + + parse_jwks(GOOGLE_JWK_BYTES, OIDCProvider::Google) + .unwrap() + .iter() + .for_each(|content| { + assert_eq!(content.0 .1, OIDCProvider::Google.get_config().0); + }); + + parse_jwks(TWITCH_JWK_BYTES, OIDCProvider::Twitch) + .unwrap() + .iter() + .for_each(|content| { + assert_eq!(content.0 .1, OIDCProvider::Twitch.get_config().0); + }); + + parse_jwks(FACEBOOK_JWK_BYTES, OIDCProvider::Facebook) + .unwrap() + .iter() + .for_each(|content| { + assert_eq!(content.0 .1, OIDCProvider::Facebook.get_config().0); + }); - // payload index must be >= 1 - let content = ParsedMaskedContent::new(&[0; 65], 0, 0, 1); - assert!(content.is_err()); + assert!(parse_jwks(BAD_JWK_BYTES, OIDCProvider::Twitch).is_err()); - // value at (payload index - 1) must be "." - // TODO: cover all parsed masked content logic + assert!(parse_jwks( + r#"{ + "something":[] + }"# + .as_bytes(), + OIDCProvider::Twitch + ) + .is_err()); +} + +#[tokio::test] +async fn test_get_jwks() { + let res = fetch_jwks().await; + assert!(res.is_ok()); + assert!(!res.unwrap().is_empty()); +} + +#[test] +fn test_get_nonce() { + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pk_bytes = vec![0x00]; + eph_pk_bytes.extend(kp.public().as_ref()); + let nonce = get_nonce(&eph_pk_bytes, 10, "100681567828351849884072155819400689117").unwrap(); + assert_eq!(nonce, "hTPpgF7XAKbW37rEUS6pEVZqmoI"); } diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs new file mode 100644 index 0000000000..f4d9bb8a61 --- /dev/null +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -0,0 +1,85 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::bn254::poseidon::PoseidonWrapper; +use crate::bn254::zk_login::AddressParams; +use crate::bn254::zk_login::OIDCProvider; +use crate::bn254::zk_login_api::Bn254Fr; +use fastcrypto::error::FastCryptoError; +use fastcrypto::hash::{Blake2b256, HashFunction}; +use fastcrypto::rsa::Base64UrlUnpadded; +use fastcrypto::rsa::Encoding; +use num_bigint::BigUint; +use std::str::FromStr; + +const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; + +/// Calculate the Sui address based on address seed and address params. +pub fn get_enoki_address(address_seed: String, param: AddressParams) -> [u8; 32] { + let mut hasher = Blake2b256::default(); + hasher.update([ZK_LOGIN_AUTHENTICATOR_FLAG]); + // unwrap is safe here + hasher.update(bcs::to_bytes(&AddressParams::new(param.iss, param.aud)).unwrap()); + hasher.update(big_int_str_to_bytes(&address_seed)); + hasher.finalize().digest +} + +/// Return the OIDC URL for the given parameters. Crucially the nonce is computed. +pub fn get_oidc_url( + provider: OIDCProvider, + eph_pk_bytes: &[u8], + max_epoch: u64, + client_id: &str, + redirect_url: &str, + jwt_randomness: &str, +) -> Result { + let nonce = get_nonce(eph_pk_bytes, max_epoch, jwt_randomness)?; + Ok(match provider { + OIDCProvider::Google => format!("https://accounts.google.com/o/oauth2/v2/auth?client_id={}&response_type=id_token&redirect_uri={}&scope=openid&nonce={}", client_id, redirect_url, nonce), + OIDCProvider::Twitch => format!("https://id.twitch.tv/oauth2/authorize?client_id={}&force_verify=true&lang=en&login_type=login&redirect_uri={}&response_type=id_token&scope=openid&nonce={}", client_id, redirect_url, nonce), + OIDCProvider::Facebook => format!("https://www.facebook.com/v17.0/dialog/oauth?client_id={}&redirect_uri={}&scope=openid&nonce={}&response_type=id_token", client_id, redirect_url, nonce) }) +} + +/// Calculate the nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs: +/// first half of eph_pk_bytes in BigInt, second half of eph_pk_bytes in BigInt, max_epoch and jwt_randomness. +pub fn get_nonce( + eph_pk_bytes: &[u8], + max_epoch: u64, + jwt_randomness: &str, +) -> Result { + let mut poseidon = PoseidonWrapper::new(); + let (first, second) = split_to_two_frs(eph_pk_bytes)?; + + let max_epoch = Bn254Fr::from_str(&max_epoch.to_string()).unwrap(); + let jwt_randomness = Bn254Fr::from_str(jwt_randomness).unwrap(); + + let hash = poseidon + .hash(vec![first, second, max_epoch, jwt_randomness]) + .unwrap(); + let data = big_int_str_to_bytes(&hash.to_string()); + let truncated = &data[data.len() - 20..]; + let mut buf = vec![0; Base64UrlUnpadded::encoded_len(truncated)]; + Ok(Base64UrlUnpadded::encode(truncated, &mut buf) + .unwrap() + .to_string()) +} + +/// Given a 33-byte public key bytes (flag || pk_bytes), returns the two Bn254Fr split at the 128 bit index. +pub fn split_to_two_frs(eph_pk_bytes: &[u8]) -> Result<(Bn254Fr, Bn254Fr), FastCryptoError> { + // Split the bytes deterministically such that the first element contains the first 128 + // bits of the hash, and the second element contains the latter ones. + let (first_half, second_half) = eph_pk_bytes.split_at(eph_pk_bytes.len() - 16); + let first_bigint = BigUint::from_bytes_be(first_half); + let second_bigint = BigUint::from_bytes_be(second_half); + + let eph_public_key_0 = Bn254Fr::from(first_bigint); + let eph_public_key_1 = Bn254Fr::from(second_bigint); + Ok((eph_public_key_0, eph_public_key_1)) +} + +/// Convert a big int string to a big endian bytearray. +pub fn big_int_str_to_bytes(value: &str) -> Vec { + BigUint::from_str(value) + .expect("Invalid big int string") + .to_bytes_be() +} diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 502170c320..e5a3f2002b 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -1,494 +1,382 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use ark_crypto_primitives::snark::SNARK; -use std::collections::HashMap; -use std::fmt; - -use super::{poseidon::PoseidonWrapper, verifier::process_vk_special}; -use crate::bn254::VerifyingKey as Bn254VerifyingKey; -use crate::circom::CircomPublicInputs; -use crate::{ - bn254::verifier::PreparedVerifyingKey, - circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}, +use fastcrypto::error::FastCryptoResult; +use serde_json::Value; + +use super::{ + poseidon::{to_poseidon_hash, PoseidonWrapper}, + utils::split_to_two_frs, }; +use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; -use ark_groth16::{Groth16, Proof, VerifyingKey}; +use ark_groth16::Proof; pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use fastcrypto::{ error::FastCryptoError, rsa::{Base64UrlUnpadded, Encoding}, }; -use num_bigint::{BigInt, BigUint}; -use once_cell::sync::Lazy; -use regex::Regex; +use num_bigint::BigUint; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json::Value; use std::str::FromStr; +type ParsedJWKs = Vec<((String, String), JWK)>; #[cfg(test)] #[path = "unit_tests/zk_login_tests.rs"] mod zk_login_tests; -static GLOBAL_VERIFYING_KEY: Lazy = Lazy::new(global_pvk); - -/// Hardcoded mapping from the provider and its supported key claim name to its map-to-field Big Int in string. -/// The field value is computed from the max key claim length and its provider. -static SUPPORTED_KEY_CLAIM_TO_FIELD: Lazy> = Lazy::new(|| { - let mut map = HashMap::new(); - map.insert( - ( - OAuthProvider::Google.get_config().0, - SupportedKeyClaim::Sub.to_string(), - ), - "18523124550523841778801820019979000409432455608728354507022210389496924497355", - ); - map.insert( - ( - OAuthProvider::Google.get_config().0, - SupportedKeyClaim::Email.to_string(), - ), - "", - ); - map.insert( - ( - OAuthProvider::Twitch.get_config().0, - SupportedKeyClaim::Sub.to_string(), - ), - "", - ); - map.insert( - ( - OAuthProvider::Twitch.get_config().0, - SupportedKeyClaim::Email.to_string(), - ), - "", - ); - map -}); - -/// Supported OAuth providers. Must contain "openid" in "scopes_supported" -/// and "public" for "subject_types_supported" instead of "pairwise". -#[derive(Debug)] -pub enum OAuthProvider { +const MAX_HEADER_LEN: u16 = 500; +const PACK_WIDTH: u16 = 248; +const ISS: &str = "iss"; +const AUD: &str = "aud"; +const NUM_EXTRACTABLE_STRINGS: u8 = 5; +const MAX_EXTRACTABLE_STR_LEN: u16 = 150; +const MAX_EXTRACTABLE_STR_LEN_B64: u16 = 4 * (1 + MAX_EXTRACTABLE_STR_LEN / 3); + +/// Supported OAuth providers. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum OIDCProvider { /// See https://accounts.google.com/.well-known/openid-configuration Google, /// See https://id.twitch.tv/oauth2/.well-known/openid-configuration Twitch, + /// See https://www.facebook.com/.well-known/openid-configuration/ + Facebook, +} + +/// Struct that contains all the OAuth provider information. A list of them can +/// be retrieved from the JWK endpoint (e.g. ) +/// and published on the bulletin along with a trusted party's signature. +// #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +#[derive(Hash, Debug, Clone, Serialize, Deserialize)] +pub struct JWK { + /// Key type parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 + pub kty: String, + /// RSA public exponent, https://datatracker.ietf.org/doc/html/rfc7517#section-9.3 + pub e: String, + /// RSA modulus, https://datatracker.ietf.org/doc/html/rfc7517#section-9.3 + pub n: String, + /// Algorithm parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + pub alg: String, +} + +/// Reader struct to parse all fields. +// #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] +pub struct JWKReader { + e: String, + n: String, + #[serde(rename = "use")] + my_use: String, + kid: String, + kty: String, + alg: String, +} + +impl JWK { + /// Parse JWK from the reader struct. + pub fn from_reader(reader: JWKReader) -> FastCryptoResult { + let trimmed_e = trim(reader.e); + if reader.alg != "RS256" + || reader.my_use != "sig" + || reader.kty != "RSA" + || trimmed_e != "AQAB" + { + return Err(FastCryptoError::InvalidInput); + } + Ok(Self { + kty: reader.kty, + e: trimmed_e, + n: trim(reader.n), + alg: reader.alg, + }) + } } -impl OAuthProvider { - /// Returns a tuple of iss string and JWK endpoint string for the given provider. +/// Trim trailing '=' so that it is considered a valid base64 url encoding string by base64ct library. +fn trim(str: String) -> String { + str.trim_end_matches('=').to_owned() +} + +/// Fetch JWKs from all supported OAuth providers and return the list as ((iss, kid), JWK) +pub async fn fetch_jwks() -> Result { + let client = reqwest::Client::new(); + let mut res = Vec::new(); + // We currently support three providers: Google, Facebook, and Twitch. + for provider in [ + OIDCProvider::Google, + OIDCProvider::Facebook, + OIDCProvider::Twitch, + ] { + let response = client + .get(provider.get_config().1) + .send() + .await + .map_err(|_| FastCryptoError::GeneralError("Failed to get JWK".to_string()))?; + let bytes = response + .bytes() + .await + .map_err(|_| FastCryptoError::GeneralError("Failed to get bytes".to_string()))?; + res.append(&mut parse_jwks(&bytes, provider)?) + } + Ok(res) +} + +/// Parse the JWK bytes received from the oauth provider keys endpoint into a map from kid to +/// JWK. +pub fn parse_jwks( + json_bytes: &[u8], + provider: OIDCProvider, +) -> Result { + let json_str = String::from_utf8_lossy(json_bytes); + let parsed_list: Result = serde_json::from_str(&json_str); + if let Ok(parsed_list) = parsed_list { + if let Some(keys) = parsed_list["keys"].as_array() { + let mut ret = Vec::new(); + for k in keys { + let parsed: JWKReader = serde_json::from_value(k.clone()) + .map_err(|_| FastCryptoError::GeneralError("Parse error".to_string()))?; + + ret.push(( + (parsed.kid.clone(), provider.get_config().0.to_owned()), + JWK::from_reader(parsed)?, + )); + } + return Ok(ret); + } + } + Err(FastCryptoError::GeneralError( + "Invalid JWK response".to_string(), + )) +} + +impl OIDCProvider { + /// Returns a tuple of iss string and the JWK url string for the given provider. pub fn get_config(&self) -> (&str, &str) { match self { - OAuthProvider::Google => ( + OIDCProvider::Google => ( "https://accounts.google.com", "https://www.googleapis.com/oauth2/v2/certs", ), - OAuthProvider::Twitch => ( + OIDCProvider::Twitch => ( "https://id.twitch.tv/oauth2", "https://id.twitch.tv/oauth2/keys", ), + OIDCProvider::Facebook => ( + "https://www.facebook.com", + "https://www.facebook.com/.well-known/oauth/openid/jwks/", + ), } } +} - /// Returns the provider for the given iss string. - pub fn from_iss(iss: &str) -> Result { - match iss { - "https://accounts.google.com" => Ok(Self::Google), - "https://id.twitch.tv/oauth2" => Ok(Self::Twitch), - _ => Err(FastCryptoError::InvalidInput), - } - } +/// Necessary value for claim. +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct Claim { + name: String, + value_base64: String, + index_mod_4: u8, } -/// The claims in the body signed by OAuth provider that must -/// be locally unique to the provider and cannot be reassigned. -#[derive(Debug)] -pub enum SupportedKeyClaim { - /// Subject id representing an unique account. - Sub, - /// Email string representing an unique account. - Email, +/// Struct that represents a standard JWT header according to +/// https://openid.net/specs/openid-connect-core-1_0.html +#[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +pub struct JWTHeader { + alg: String, + kid: String, + typ: String, } -impl fmt::Display for SupportedKeyClaim { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SupportedKeyClaim::Email => write!(f, "email"), - SupportedKeyClaim::Sub => write!(f, "sub"), +impl JWTHeader { + /// Parse the header base64 string into a [struct JWTHeader]. + pub fn new(header_base64: &str) -> Result { + let header_bytes = Base64UrlUnpadded::decode_vec(header_base64) + .map_err(|_| FastCryptoError::InvalidInput)?; + let header_str = + std::str::from_utf8(&header_bytes).map_err(|_| FastCryptoError::InvalidInput)?; + let header: JWTHeader = + serde_json::from_str(header_str).map_err(|_| FastCryptoError::InvalidInput)?; + if header.alg != "RS256" || header.typ != "JWT" { + return Err(FastCryptoError::GeneralError("Invalid header".to_string())); } + Ok(header) } } -/// Return whether the claim string is supported for zk login. -pub fn is_claim_supported(claim_name: &str) -> bool { - vec![SupportedKeyClaim::Sub.to_string()].contains(&claim_name.to_owned()) +/// A structed of all parsed and validated values from the masked content bytes. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct JWTDetails { + kid: String, + header: String, + iss: String, + aud: String, } -/// Verify a zk login proof using the fixed verifying key. -pub fn verify_zk_login_proof_with_fixed_vk( - proof: &ZkLoginProof, - public_inputs: &PublicInputs, -) -> Result { - Groth16::::verify_with_processed_vk( - &GLOBAL_VERIFYING_KEY.as_arkworks_pvk(), - &public_inputs.as_arkworks(), - &proof.as_arkworks(), - ) - .map_err(|e| FastCryptoError::GeneralError(e.to_string())) -} +impl JWTDetails { + /// Read a list of Claims and header string and parse them into fields + /// header, iss, iss_index, aud, aud_index. + pub fn new(header_base64: &str, claims: &[Claim]) -> Result { + let header = JWTHeader::new(header_base64)?; + let claim = claims + .get(0) + .ok_or_else(|| FastCryptoError::GeneralError("Invalid claim".to_string()))?; + if claim.name != ISS { + return Err(FastCryptoError::GeneralError( + "iss not found in claims".to_string(), + )); + } + let ext_iss = decode_base64_url(&claim.value_base64, &claim.index_mod_4)?; -/// Load a fixed verifying key from zklogin.vkey output from setup -/// https://github.com/MystenLabs/fastcrypto/blob/2a704431e4d2685625c0cc06d19fd7d08a4aafa4/openid-zkp-auth/README.md -fn global_pvk() -> PreparedVerifyingKey { - // Convert the Circom G1/G2/GT to arkworks G1/G2/GT - let vk_alpha_1 = g1_affine_from_str_projective(vec![ - "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), - "9383485363053290200918347156157836566562967994039712273449902621266178545958".to_string(), - "1".to_string(), - ]); - let vk_beta_2 = g2_affine_from_str_projective(vec![ - vec![ - "6375614351688725206403948262868962793625744043794305715222011528459656738731" - .to_string(), - "4252822878758300859123897981450591353533073413197771768651442665752259397132" - .to_string(), - ], - vec![ - "10505242626370262277552901082094356697409835680220590971873171140371331206856" - .to_string(), - "21847035105528745403288232691147584728191162732299865338377159692350059136679" - .to_string(), - ], - vec!["1".to_string(), "0".to_string()], - ]); - let vk_gamma_2 = g2_affine_from_str_projective(vec![ - vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), - ], - vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), - ], - vec!["1".to_string(), "0".to_string()], - ]); - let vk_delta_2 = g2_affine_from_str_projective(vec![ - vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), - ], - vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), - ], - vec!["1".to_string(), "0".to_string()], - ]); + let claim_2 = claims + .get(1) + .ok_or_else(|| FastCryptoError::GeneralError("Invalid claim".to_string()))?; + if claim_2.name != AUD { + return Err(FastCryptoError::GeneralError( + "aud not found in claims".to_string(), + )); + } + let ext_aud = decode_base64_url(&claim_2.value_base64, &claim_2.index_mod_4)?; - // Create a vector of G1Affine elements from the IC - let mut vk_gamma_abc_g1 = Vec::new(); - for e in vec![ - vec![ - "18931764958316061396537365316410279129357566768168194299771466990652581507745" - .to_string(), - "19589594864158083697499253358172374190940731232487666687594341722397321059767" - .to_string(), - "1".to_string(), - ], - vec![ - "6267760579143073538587735682191258967573139158461221609828687320377758856284" - .to_string(), - "18672820669757254021555424652581702101071897282778751499312181111578447239911" - .to_string(), - "1".to_string(), - ], - ] { - let g1 = g1_affine_from_str_projective(e); - vk_gamma_abc_g1.push(g1); + Ok(JWTDetails { + kid: header.kid, + header: header_base64.to_string(), + iss: verify_extended_claim(&ext_iss, ISS)?, + aud: verify_extended_claim(&ext_aud, AUD)?, + }) } - - let vk = VerifyingKey { - alpha_g1: vk_alpha_1, - beta_g2: vk_beta_2, - gamma_g2: vk_gamma_2, - delta_g2: vk_delta_2, - gamma_abc_g1: vk_gamma_abc_g1, - }; - process_vk_special(&Bn254VerifyingKey(vk)) } -/// A parsed result of all aux inputs. -#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] -pub struct AuxInputs { - addr_seed: String, - eph_public_key: Vec, - jwt_sha2_hash: Vec, - jwt_signature: String, - key_claim_name: String, - masked_content: Vec, - max_epoch: u64, - num_sha2_blocks: u8, - payload_len: u16, - payload_start_index: u16, +/// All inputs required for the zk login proof verification and other auxiliary inputs. +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct ZkLoginInputs { + proof_points: ZkLoginProof, + address_seed: String, + claims: Vec, + header_base64: String, + #[serde(skip)] + parsed_masked_content: JWTDetails, #[serde(skip)] - parsed_masked_content: ParsedMaskedContent, + all_inputs_hash: Vec, } -impl AuxInputs { +impl ZkLoginInputs { /// Validate and parse masked content bytes into the struct and other json strings into the struct. pub fn from_json(value: &str) -> Result { - let mut inputs: AuxInputs = + let inputs: ZkLoginInputs = serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidInput)?; - inputs.parsed_masked_content = ParsedMaskedContent::new( - &inputs.masked_content, - inputs.payload_start_index, - inputs.payload_len, - inputs.num_sha2_blocks, - )?; Ok(inputs) } - /// Init ParsedMaskedContent + /// Initialize JWTDetails pub fn init(&mut self) -> Result { - self.parsed_masked_content = ParsedMaskedContent::new( - &self.masked_content, - self.payload_start_index, - self.payload_len, - self.num_sha2_blocks, - )?; + self.parsed_masked_content = JWTDetails::new(&self.header_base64, &self.claims)?; Ok(self.to_owned()) } - /// Get the jwt hash in byte array format. - pub fn get_jwt_hash(&self) -> Vec { - self.jwt_sha2_hash - .iter() - .flat_map(|x| big_int_str_to_bytes(x)) - .collect() - } - /// Get the ephemeral pubkey in bytes. - pub fn get_eph_pub_key(&self) -> Vec { - self.eph_public_key - .iter() - .flat_map(|x| big_int_str_to_bytes(x)) - .collect() - } - - /// Get the max epoch value. - pub fn get_max_epoch(&self) -> u64 { - self.max_epoch - } - - /// Get jwt signature in bytes. - pub fn get_jwt_signature(&self) -> Result, FastCryptoError> { - Base64UrlUnpadded::decode_vec(&self.jwt_signature) - .map_err(|_| FastCryptoError::InvalidInput) - } - - /// Get the address seed in string. - pub fn get_address_seed(&self) -> &str { - &self.addr_seed + /// Get the parsed kid string. + pub fn get_kid(&self) -> &str { + &self.parsed_masked_content.kid } - /// Get the iss string. + /// Get the parsed iss string. pub fn get_iss(&self) -> &str { - self.parsed_masked_content.get_iss() + &self.parsed_masked_content.iss } - /// Get the client id string. - pub fn get_client_id(&self) -> &str { - self.parsed_masked_content.get_client_id() + /// Get the parsed aud string. + pub fn get_aud(&self) -> &str { + &self.parsed_masked_content.aud } - /// Get the kid string. - pub fn get_kid(&self) -> &str { - self.parsed_masked_content.get_kid() + /// Get zk login proof. + pub fn get_proof(&self) -> &ZkLoginProof { + &self.proof_points } - /// Get the key claim name string. - pub fn get_claim_name(&self) -> &str { - &self.key_claim_name + /// Get public inputs in arkworks format. + pub fn get_public_inputs(&self) -> &[Bn254Fr] { + &self.all_inputs_hash } - /// Calculate the poseidon hash from 10 selected fields in the aux inputs. - pub fn calculate_all_inputs_hash(&self) -> Result { - // TODO(joyqvq): check each string for bigint is valid. - let mut poseidon = PoseidonWrapper::new(); - let jwt_sha2_hash_0 = Bn254Fr::from_str(&self.jwt_sha2_hash[0]).unwrap(); - let jwt_sha2_hash_1 = Bn254Fr::from_str(&self.jwt_sha2_hash[1]).unwrap(); - let masked_content_hash = Bn254Fr::from_str(&self.parsed_masked_content.hash).unwrap(); - let payload_start_index = Bn254Fr::from_str(&self.payload_start_index.to_string()).unwrap(); - let payload_len = Bn254Fr::from_str(&self.payload_len.to_string()).unwrap(); - let eph_public_key_0 = Bn254Fr::from_str(&self.eph_public_key[0]).unwrap(); - let eph_public_key_1 = Bn254Fr::from_str(&self.eph_public_key[1]).unwrap(); - let max_epoch = Bn254Fr::from_str(&self.max_epoch.to_string()).unwrap(); - let num_sha2_blocks = Bn254Fr::from_str(&self.num_sha2_blocks.to_string()).unwrap(); - let addr_seed = Bn254Fr::from_str(&self.addr_seed.to_string()).unwrap(); - let key_claim_name_f = Bn254Fr::from_str( - SUPPORTED_KEY_CLAIM_TO_FIELD - .get(&(self.get_iss(), self.get_claim_name().to_owned())) - .unwrap(), - ) - .unwrap(); - Ok(poseidon - .hash(vec![ - jwt_sha2_hash_0, - jwt_sha2_hash_1, - masked_content_hash, - payload_start_index, - payload_len, - eph_public_key_0, - eph_public_key_1, - max_epoch, - num_sha2_blocks, - key_claim_name_f, - addr_seed, - ])? - .to_string()) + /// Get address seed string. + pub fn get_address_seed(&self) -> &str { + &self.address_seed } -} - -/// A structed of all parsed and validated values from the masked content bytes. -#[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -pub struct ParsedMaskedContent { - header: JWTHeader, - iss: String, - client_id: String, - hash: String, -} -/// Struct that represents a standard JWT header according to -/// https://openid.net/specs/openid-connect-core-1_0.html -#[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -pub struct JWTHeader { - alg: String, - kid: String, - typ: String, -} + /// Get address seed string. + pub fn get_address_params(&self) -> AddressParams { + AddressParams::new(self.get_iss().to_owned(), self.get_aud().to_owned()) + } -impl ParsedMaskedContent { - /// Parse the masked content bytes into a [struct ParsedMaskedContent]. - /// payload_start_index, payload_len, num_sha2_blocks are used for - /// validation and parsing. - pub fn new( - masked_content: &[u8], - payload_start_index: u16, - payload_len: u16, - num_sha2_blocks: u8, - ) -> Result { - // Verify the bytes after 64 * num_sha2_blocks should be all 0s. - if !masked_content - .get(64 * num_sha2_blocks as usize..) - .ok_or(FastCryptoError::InvalidInput)? - .iter() - .all(|&x| x == 0) - { - return Err(FastCryptoError::GeneralError( - "Incorrect payload padding".to_string(), - )); + /// Calculate the poseidon hash from selected fields from inputs, along with the ephemeral pubkey. + pub fn calculate_all_inputs_hash( + &self, + eph_pk_bytes: &[u8], + modulus: &[u8], + max_epoch: u64, + ) -> Result, FastCryptoError> { + if self.header_base64.len() > MAX_HEADER_LEN as usize { + return Err(FastCryptoError::GeneralError("Header too long".to_string())); } - let masked_content_tmp = masked_content - .get(..64 * num_sha2_blocks as usize) - .ok_or(FastCryptoError::InvalidInput)?; - - // Verify the byte at payload start index is indeed b'.'. - if payload_start_index < 1 - || masked_content_tmp.get(payload_start_index as usize - 1) != Some(&b'.') - { - return Err(FastCryptoError::GeneralError( - "Incorrect payload index for separator".to_string(), - )); - } - - let header = parse_and_validate_header( - masked_content_tmp - .get(0..payload_start_index as usize - 1) - .ok_or_else(|| { - FastCryptoError::GeneralError( - "Invalid payload index to parse header".to_string(), - ) - })?, - )?; - - // Parse the jwt length from the last 8 bytes of the masked content. - let jwt_length_bytes = masked_content_tmp - .get(masked_content_tmp.len() - 8..) - .ok_or_else(|| FastCryptoError::GeneralError("Invalid last 8 bytes".to_string()))?; - let jwt_length = calculate_value_from_bytearray(jwt_length_bytes); - - // Verify the jwt length equals to 8*(payload_start_index + payload_len). - if jwt_length != 8 * (payload_start_index as usize + payload_len as usize) { - return Err(FastCryptoError::GeneralError( - "Incorrect jwt length".to_string(), - )); + let mut poseidon = PoseidonWrapper::new(); + let addr_seed = to_field(&self.address_seed)?; + let (first, second) = split_to_two_frs(eph_pk_bytes)?; + + let max_epoch = to_field(&max_epoch.to_string())?; + let mut padded_claims = self.claims.clone(); + for _ in self.claims.len()..NUM_EXTRACTABLE_STRINGS as usize { + padded_claims.push(Claim { + name: "dummy".to_string(), + value_base64: "e".to_string(), + index_mod_4: 0, + }); } - - // Parse sha2 pad into a bit array. - let sha_2_pad = bytearray_to_bits( - &masked_content_tmp[payload_start_index as usize + payload_len as usize..], - ); - - // Verify that the first bit of the bit array of sha2 pad is 1. - if !sha_2_pad[0] { - return Err(FastCryptoError::GeneralError( - "Incorrect sha2 padding".to_string(), - )); + let mut claim_f = Vec::new(); + for i in 0..NUM_EXTRACTABLE_STRINGS { + let val = &padded_claims[i as usize].value_base64; + if val.len() > MAX_EXTRACTABLE_STR_LEN_B64 as usize { + return Err(FastCryptoError::GeneralError( + "Invalid claim length".to_string(), + )); + } + claim_f.push(hash_ascii_str_to_field( + &padded_claims[i as usize].value_base64, + MAX_EXTRACTABLE_STR_LEN_B64, + )?); } - - // Verify the count of 0s in the sha2 pad bit array satifies the condition - // with the jwt length. - validate_zeros_count(&sha_2_pad, jwt_length)?; - - // Splits the masked payload into 3 parts (that reveals iss, aud, nonce respectively) - // separated by a delimiter of "=" of any length. With padding etc - let parts = find_parts_and_indices( - &masked_content_tmp - [payload_start_index as usize..payload_start_index as usize + payload_len as usize], + let mut poseidon_claim = PoseidonWrapper::new(); + let extracted_claims_hash = poseidon_claim.hash(claim_f)?; + + let mut poseidon_index = PoseidonWrapper::new(); + let extracted_index_hash = poseidon_index.hash( + padded_claims + .iter() + .map(|c| to_field(&c.index_mod_4.to_string()).unwrap()) + .collect::>(), )?; - - Ok(Self { - header, - iss: parts[0].to_string(), - client_id: parts[1].to_string(), - hash: calculate_merklized_hash(masked_content)?, - }) - } - - /// Get the iss string value. - pub fn get_iss(&self) -> &str { - &self.iss - } - - /// Get the kid string value. - pub fn get_kid(&self) -> &str { - &self.header.kid - } - - /// Get the client id string value. - pub fn get_client_id(&self) -> &str { - &self.client_id + let header_f = hash_ascii_str_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; + let modulus_f = hash_to_field(&[BigUint::from_bytes_be(modulus)], 2048, PACK_WIDTH)?; + Ok(vec![poseidon.hash(vec![ + first, + second, + addr_seed, + max_epoch, + extracted_claims_hash, + extracted_index_hash, + header_f, + modulus_f, + ])?]) } } - /// The zk login proof. #[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] pub struct ZkLoginProof { pi_a: Vec, pi_b: Vec>, pi_c: Vec, - protocol: String, } impl ZkLoginProof { @@ -496,10 +384,7 @@ impl ZkLoginProof { pub fn from_json(value: &str) -> Result { let proof: ZkLoginProof = serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidProof)?; - match proof.protocol == "groth16" { - true => Ok(proof), - false => Err(FastCryptoError::InvalidProof), - } + Ok(proof) } /// Convert the Circom G1/G2/GT to arkworks G1/G2/GT @@ -511,113 +396,82 @@ impl ZkLoginProof { } } -/// The public inputs containing an array of string that is the all inputs hash. -#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -pub struct PublicInputs { - inputs: Vec, // Represented the public inputs in canonical serialized form. +/// Parse the extended claim json value to its claim value, using the expected claim key. +fn verify_extended_claim( + extended_claim: &str, + expected_key: &str, +) -> Result { + // Last character of each extracted_claim must be '}' or ',' + if !(extended_claim.ends_with('}') || extended_claim.ends_with(',')) { + return Err(FastCryptoError::GeneralError( + "Invalid extended claim".to_string(), + )); + } + + let json_str = format!("{{{}}}", &extended_claim[..extended_claim.len() - 1]); + let json: Value = serde_json::from_str(&json_str).map_err(|_| FastCryptoError::InvalidInput)?; + let value = json + .as_object() + .ok_or(FastCryptoError::InvalidInput)? + .get(expected_key) + .ok_or(FastCryptoError::InvalidInput)? + .as_str() + .ok_or(FastCryptoError::InvalidInput)?; + Ok(value.to_string()) } -impl PublicInputs { - /// Parse the public inputs from a json string. - pub fn from_json(value: &str) -> Result { - let inputs: CircomPublicInputs = - serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidProof)?; - Ok(Self { inputs }) - } - - /// Convert the public inputs into arkworks format. - pub fn as_arkworks(&self) -> Vec { - // TODO(joyqvq): check safety for valid bigint string. - self.inputs - .iter() - .map(|x| Bn254Fr::from_str(x).unwrap()) - .collect() +/// Parse the base64 string, add paddings based on offset, and convert to a bytearray. +fn decode_base64_url(s: &str, i: &u8) -> Result { + if s.len() < 2 { + return Err(FastCryptoError::GeneralError( + "Base64 string smaller than 2".to_string(), + )); + } + let mut bits = base64_to_bitarray(s); + match i { + 0 => {} + 1 => { + bits.drain(..2); + } + 2 => { + bits.drain(..4); + } + _ => { + return Err(FastCryptoError::GeneralError( + "Invalid first_char_offset".to_string(), + )); + } } - /// Get the all_inputs_hash as big int string. - pub fn get_all_inputs_hash(&self) -> &str { - &self.inputs[0] - } -} - -/// Parse the ascii string from the input bytearray and split it by delimiter "=" of any -/// length. Return a list of the split parts and a list of start indices of each part. -fn find_parts_and_indices(input: &[u8]) -> Result, FastCryptoError> { - let input_str = std::str::from_utf8(input) - .map_err(|_| FastCryptoError::GeneralError("Invalid masked content".to_string()))?; - let re = Regex::new("=+").expect("Regex string should be valid"); - - let mut chunks = Vec::new(); - let mut start_idx = 0; - - for mat in re.find_iter(input_str) { - let end_idx = mat.start(); - if start_idx < end_idx { - if start_idx % 4 == 3 || end_idx % 4 == 0 { - return Err(FastCryptoError::GeneralError( - "Invalid start or end index".to_string(), - )); - } - let mut chunk_in_bits: Vec = base64_to_bitarray(&input_str[start_idx..end_idx]); - let original_len = chunk_in_bits.len(); - if start_idx % 4 == 1 { - chunk_in_bits.drain(..2); - } else if start_idx % 4 == 2 { - chunk_in_bits.drain(..4); - } - - if end_idx % 4 == 1 { - chunk_in_bits.drain(original_len - 4..); - } else if end_idx % 4 == 2 { - chunk_in_bits.drain(original_len - 2..); - }; - - let bytearray = bits_to_bytes(&chunk_in_bits); - let input_str = std::str::from_utf8(&bytearray).map_err(|_| { - FastCryptoError::GeneralError("Invalid bytearray from tweaked bits".to_string()) - })?; - chunks.push(input_str.to_string()); + let last_char_offset = (i + s.len() as u8 - 1) % 4; + match last_char_offset { + 3 => {} + 2 => { + bits.drain(bits.len() - 2..); + } + 1 => { + bits.drain(bits.len() - 4..); + } + _ => { + return Err(FastCryptoError::GeneralError( + "Invalid last_char_offset".to_string(), + )); } - start_idx = mat.end(); } - Ok(vec![ - find_value(&chunks[0], "\"iss\":\"", "\",")?, - find_value(&chunks[1], "\"aud\":\"", "\",")?, - ]) -} -/// Given a part in string, find the value between the prefix and suffix. -/// The index value is used to decide the number of '0' needed to pad to -/// make the parts an valid Base64 encoding. -fn find_value(ascii_string: &str, prefix: &str, suffix: &str) -> Result { - let start = ascii_string - .find(prefix) - .ok_or_else(|| FastCryptoError::GeneralError("Invalid parts prefix".to_string()))? - + prefix.len(); - let end = ascii_string[start..] - .find(suffix) - .ok_or_else(|| FastCryptoError::GeneralError("Invalid ascii suffix".to_string()))? - + start; - Ok(ascii_string[start..end].to_string()) -} - -/// Count the number of 0s in the bit array and check if the count satifies as the -/// smallest, non-negative solution to equation jwt_length + 1 + K = 448 (mod 512). -/// See more at 4.1(b) https://datatracker.ietf.org/doc/html/rfc4634#section-4.1 -fn validate_zeros_count(arr: &[bool], jwt_length: usize) -> Result<(), FastCryptoError> { - // Count the number of 0s in the bitarray excluding the last 8 bytes (64 bits). - let count = arr.iter().take(arr.len() - 64).filter(|&bit| !bit).count(); - if (jwt_length + 1 + count) % 512 == 448 && count < 512 { - Ok(()) - } else { - Err(FastCryptoError::GeneralError( - "Invalid bitarray".to_string(), - )) + if bits.len() % 8 != 0 { + return Err(FastCryptoError::GeneralError( + "Invalid bits length".to_string(), + )); } + + Ok(std::str::from_utf8(&bitarray_to_bytearray(&bits)) + .map_err(|_| FastCryptoError::GeneralError("Invalid UTF8 string".to_string()))? + .to_owned()) } /// Map a base64 string to a bit array by taking each char's index and covert it to binary form. -fn base64_to_bitarray(input: &str) -> Vec { +fn base64_to_bitarray(input: &str) -> Vec { let base64_url_character_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; input @@ -626,21 +480,21 @@ fn base64_to_bitarray(input: &str) -> Vec { let index = base64_url_character_set.find(c).unwrap(); let mut bits = Vec::new(); for i in 0..6 { - bits.push((index >> (5 - i)) & 1 == 1); + bits.push(u8::from((index >> (5 - i)) & 1 == 1)); } bits }) .collect() } -/// Convert a bitarray to a bytearray. -fn bits_to_bytes(bits: &[bool]) -> Vec { +/// Convert a bitarray (each bit is represented by u8) to a bytearray by taking each 8 bits as a byte. +fn bitarray_to_bytearray(bits: &[u8]) -> Vec { let mut bytes: Vec = Vec::new(); let mut current_byte: u8 = 0; let mut bits_remaining: u8 = 8; for bit in bits.iter() { - if *bit { + if bit == &1 { current_byte |= 1 << (bits_remaining - 1); } bits_remaining -= 1; @@ -658,99 +512,97 @@ fn bits_to_bytes(bits: &[bool]) -> Vec { bytes } -/// Convert a big int string to a big endian bytearray. -pub fn big_int_str_to_bytes(value: &str) -> Vec { - BigInt::from_str(value) - .expect("Invalid big int string") - .to_bytes_be() - .1 +/// Convert a bigint string to a field element. +fn to_field(val: &str) -> Result { + Bn254Fr::from_str(val).map_err(|_| FastCryptoError::InvalidInput) } -/// Calculate the integer value from the bytearray. -fn calculate_value_from_bytearray(arr: &[u8]) -> usize { - let sized: [u8; 8] = arr.try_into().expect("Invalid byte array"); - ((sized[7] as u16) | (sized[6] as u16) << 8).into() +/// Pads a stream of bytes and maps it to a field element +fn hash_ascii_str_to_field(str: &str, max_size: u16) -> Result { + let str_padded = str_to_padded_char_codes(str, max_size)?; + hash_to_field(&str_padded, 8, PACK_WIDTH) } -/// Given a chunk of bytearray, parse it as an ascii string and decode as a JWTHeader. -/// Return the JWTHeader if its fields are valid. -fn parse_and_validate_header(chunk: &[u8]) -> Result { - let header_str = std::str::from_utf8(chunk) - .map_err(|_| FastCryptoError::GeneralError("Cannot parse header string".to_string()))?; - let decoded_header = Base64UrlUnpadded::decode_vec(header_str) - .map_err(|_| FastCryptoError::GeneralError("Invalid jwt header".to_string()))?; - let json_header: Value = serde_json::from_slice(&decoded_header) - .map_err(|_| FastCryptoError::GeneralError("Invalid json".to_string()))?; - let header: JWTHeader = serde_json::from_value(json_header) - .map_err(|_| FastCryptoError::GeneralError("Cannot parse jwt header".to_string()))?; - if header.alg != "RS256" || header.typ != "JWT" { - Err(FastCryptoError::GeneralError("Invalid header".to_string())) - } else { - Ok(header) - } +fn str_to_padded_char_codes(str: &str, max_len: u16) -> Result, FastCryptoError> { + let arr: Vec = str + .chars() + .map(|c| BigUint::from_slice(&([c as u32]))) + .collect(); + pad_with_zeroes(arr, max_len) } -/// Calculate the merklized hash of the given bytes after 0 paddings. -pub fn calculate_merklized_hash(bytes: &[u8]) -> Result { - let mut bitarray = bytearray_to_bits(bytes); - pad_bitarray(&mut bitarray, 248); - let bigints = convert_to_bigints(&bitarray, 248); - to_poseidon_hash(bigints) +fn pad_with_zeroes(in_arr: Vec, out_count: u16) -> Result, FastCryptoError> { + if in_arr.len() > out_count as usize { + return Err(FastCryptoError::GeneralError("in_arr too long".to_string())); + } + let mut padded = in_arr.clone(); + padded.extend(vec![ + BigUint::from_str("0").unwrap(); + out_count as usize - in_arr.len() as usize + ]); + Ok(padded) } -/// Calculate the hash of the inputs. -pub fn to_poseidon_hash(inputs: Vec) -> Result { - if inputs.len() <= 15 { - let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); - Ok(poseidon1.hash(inputs)?.to_string()) - } else if inputs.len() <= 30 { - let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); - let hash1 = poseidon1.hash(inputs[0..15].to_vec())?; - - let mut poseidon2 = PoseidonWrapper::new(); - let hash2 = poseidon2.hash(inputs[15..].to_vec())?; - - let mut poseidon3 = PoseidonWrapper::new(); - let hash_final = poseidon3.hash([hash1, hash2].to_vec()); +/// Maps a stream of bigints to a single field element. First we convert the base from +/// inWidth to packWidth. Then we compute the poseidon hash of the "packed" input. +/// input is the input vector containing equal-width big ints. inWidth is the width of +/// each input element. +fn hash_to_field( + input: &[BigUint], + in_width: u16, + pack_width: u16, +) -> Result { + let packed = convert_base(input, in_width, pack_width)?; + to_poseidon_hash(packed) +} - Ok(hash_final?.to_string()) - } else { - Err(FastCryptoError::InvalidInput) +/// Helper function to pack field elements from big ints. +fn convert_base( + in_arr: &[BigUint], + in_width: u16, + out_width: u16, +) -> Result, FastCryptoError> { + let bits = big_int_array_to_bits(in_arr, in_width as usize); + let packed: Vec = bits + .chunks(out_width as usize) + .map(|chunk| Bn254Fr::from(BigUint::from_radix_be(chunk, 2).unwrap())) + .collect(); + match packed.len() != in_arr.len() * in_width as usize / out_width as usize + 1 { + true => Err(FastCryptoError::InvalidInput), + false => Ok(packed), } } -/// Convert a bytearray to a bitarray. -fn bytearray_to_bits(bytearray: &[u8]) -> Vec { - bytearray - .iter() - .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1 == 1)) - .collect() -} -/// Convert a bitarray to a bytearray. -fn bitarray_to_string(bitarray: &[bool]) -> Vec { - bitarray.iter().map(|&b| u8::from(b)).collect() +/// Convert a big int array to a bit array with 0 paddings. +fn big_int_array_to_bits(arr: &[BigUint], int_size: usize) -> Vec { + let mut bitarray: Vec = Vec::new(); + for num in arr { + let val = num.to_radix_be(2); + let extra_bits = if val.len() < int_size { + int_size - val.len() + } else { + 0 + }; + + let mut padded = vec![0; extra_bits]; + padded.extend(val); + bitarray.extend(padded) + } + bitarray } -/// Pad the bitarray some number of 0s so that its length is a multiple of the segment size. -fn pad_bitarray(bitarray: &mut Vec, segment_size: usize) { - let remainder = bitarray.len() % segment_size; - if remainder != 0 { - bitarray.extend(std::iter::repeat(false).take(segment_size - remainder)); - } +/// Parameters for generating an address. +#[derive(Debug, Serialize, Deserialize)] +pub struct AddressParams { + /// The issuer string. + pub iss: String, + /// The audience string. + pub aud: String, } -/// Convert a bitarray to a vector of field elements, padded using segment size. -fn convert_to_bigints(bitarray: &[bool], segment_size: usize) -> Vec { - let chunks = bitarray.chunks(segment_size); - chunks - .map(|chunk| { - let mut bytes = vec![0; (segment_size + 7) / 8]; - for (i, &bit) in chunk.iter().enumerate() { - bytes[i / 8] |= (bit as u8) << (7 - i % 8); - } - let f = bitarray_to_string(chunk); - let st = BigUint::from_radix_be(&f, 2).unwrap().to_string(); - Bn254Fr::from_str(&st).unwrap() - }) - .collect() +impl AddressParams { + /// Create address params from iss and aud. + pub fn new(iss: String, aud: String) -> Self { + Self { iss, aud } + } } diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs new file mode 100644 index 0000000000..d72134a135 --- /dev/null +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -0,0 +1,164 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use ark_crypto_primitives::snark::SNARK; +use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; + +use super::verifier::process_vk_special; +use super::zk_login::{ZkLoginInputs, JWK}; +use crate::bn254::VerifyingKey as Bn254VerifyingKey; +use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; +pub use ark_bn254::{Bn254, Fr as Bn254Fr}; +pub use ark_ff::ToConstraintField; +use ark_groth16::{Groth16, PreparedVerifyingKey, Proof, VerifyingKey}; +pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use fastcrypto::error::FastCryptoError; +use once_cell::sync::Lazy; + +/// Enum to specify the environment to use for verifying keys. +#[derive(Debug)] +pub enum Environment { + /// Use the secure global verifying key derived from ceremony. + Production, + /// Use the insecure global verifying key. + Test, +} + +// TODO: Replace after ceremony. +static GLOBAL_VERIFYING_KEY: Lazy> = Lazy::new(global_pvk); +static INSECURE_GLOBAL_VERIFYING_KEY: Lazy> = Lazy::new(global_pvk); + +/// Load a fixed verifying key from zklogin.vkey output. This is based on a local setup and should not use in production. +fn global_pvk() -> PreparedVerifyingKey { + // Convert the Circom G1/G2/GT to arkworks G1/G2/GT + let vk_alpha_1 = g1_affine_from_str_projective(vec![ + "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), + "9383485363053290200918347156157836566562967994039712273449902621266178545958".to_string(), + "1".to_string(), + ]); + let vk_beta_2 = g2_affine_from_str_projective(vec![ + vec![ + "6375614351688725206403948262868962793625744043794305715222011528459656738731" + .to_string(), + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + .to_string(), + ], + vec![ + "10505242626370262277552901082094356697409835680220590971873171140371331206856" + .to_string(), + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + .to_string(), + ], + vec!["1".to_string(), "0".to_string()], + ]); + let vk_gamma_2 = g2_affine_from_str_projective(vec![ + vec![ + "10857046999023057135944570762232829481370756359578518086990519993285655852781" + .to_string(), + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + .to_string(), + ], + vec![ + "8495653923123431417604973247489272438418190587263600148770280649306958101930" + .to_string(), + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + .to_string(), + ], + vec!["1".to_string(), "0".to_string()], + ]); + let vk_delta_2 = g2_affine_from_str_projective(vec![ + vec![ + "10857046999023057135944570762232829481370756359578518086990519993285655852781" + .to_string(), + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + .to_string(), + ], + vec![ + "8495653923123431417604973247489272438418190587263600148770280649306958101930" + .to_string(), + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + .to_string(), + ], + vec!["1".to_string(), "0".to_string()], + ]); + + // Create a vector of G1Affine elements from the IC + let mut vk_gamma_abc_g1 = Vec::new(); + for e in vec![ + vec![ + "10650235292452276702815258020174876822554680558613093350826598743737711706082" + .to_string(), + "10904000006666353404839309737175457841172416892262756319513121366464849299934" + .to_string(), + "1".to_string(), + ], + vec![ + "13523860369377817188474813326919511067573805860184371020956327842962539802962" + .to_string(), + "15924113522601648253933515938165772453615741568509559656790523323812357588202" + .to_string(), + "1".to_string(), + ], + ] { + let g1 = g1_affine_from_str_projective(e); + vk_gamma_abc_g1.push(g1); + } + + let vk = VerifyingKey { + alpha_g1: vk_alpha_1, + beta_g2: vk_beta_2, + gamma_g2: vk_gamma_2, + delta_g2: vk_delta_2, + gamma_abc_g1: vk_gamma_abc_g1, + }; + + // Conver thte verifying key into the prepared form. + process_vk_special(&Bn254VerifyingKey(vk)).as_arkworks_pvk() +} + +/// Entry point for the ZkLogin API. +pub fn verify_zk_login( + input: &ZkLoginInputs, + max_epoch: u64, + eph_pubkey_bytes: &[u8], + all_jwk: &HashMap<(String, String), JWK>, + usage: Environment, +) -> Result<(), FastCryptoError> { + // Load the expected JWK based on (kid, iss). + let jwk = all_jwk + .get(&(input.get_kid().to_string(), input.get_iss().to_string())) + .ok_or_else(|| FastCryptoError::GeneralError("JWK not found".to_string()))?; + + // Decode modulus to bytes. + let modulus = Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| { + FastCryptoError::GeneralError("Invalid Base64 encoded jwk modulus".to_string()) + })?; + + // Calculat all inputs hash and passed to the verification function. + match verify_zk_login_proof_with_fixed_vk( + usage, + input.get_proof().as_arkworks(), + &input.calculate_all_inputs_hash(eph_pubkey_bytes, &modulus, max_epoch)?, + ) { + Ok(true) => Ok(()), + Ok(false) | Err(_) => Err(FastCryptoError::GeneralError( + "Groth16 proof verify failed".to_string(), + )), + } +} + +/// Verify a proof against its public inputs using the fixed verifying key. +fn verify_zk_login_proof_with_fixed_vk( + usage: Environment, + proof: Proof, + public_inputs: &[Bn254Fr], +) -> Result { + let pvk = match usage { + Environment::Production => &GLOBAL_VERIFYING_KEY, + Environment::Test => &INSECURE_GLOBAL_VERIFYING_KEY, + }; + Groth16::::verify_with_processed_vk(pvk, public_inputs, &proof) + .map_err(|e| FastCryptoError::GeneralError(e.to_string())) +}