A JWT Verification and Claims Attestation Library for Noir. This library provides a set of types and functions for verifying different JWT schemes. The current implementation supports the following JWT schemes:
- HMAC-SHA256 (HS256)
- RSA-SHA256 (RS256 with 2048-bit keys)
JWT structure is defined in JWT struct, which is generic over the signature type, header length, payload length, and signature length. Signature type is either BoundedVec<u8, MaxSignatureLength>
for HMAC or BoundedVec<Field, MaxSignatureLength>
for RSA.
pub type Header<let MaxHeaderLength: u32> = BoundedVec<u8, MaxHeaderLength>;
pub type Payload<let MaxPayloadLength: u32> = BoundedVec<u8, MaxPayloadLength>;
pub type Signature<T, let MaxSignatureLength: u32> = BoundedVec<T, MaxSignatureLength>;
pub struct JWT<T, let MaxHeaderLength: u32, let MaxPayloadLength: u32, let MaxSignatureLength: u32> {
pub header: Header<MaxHeaderLength>,
pub payload: Payload<MaxPayloadLength>,
pub signature: Signature<T, MaxSignatureLength>,
}
In your Nargo.toml file, add the version of this library you would like to install under dependency:
[dependencies]
noir_jwt = { tag = "v0.1.0", git = "https://github.com/zkpersona/noir-jwt", directory = "lib" }
use noir_jwt::JWT_H256 as JWT;
use noir_jwt::types::SecretKey;
pub global MAX_HEADER_LENGTH: u32 = 64;
pub global MAX_PAYLOAD_LENGTH: u32 = 256;
pub global MAX_SIGNATURE_LENGTH: u32 = 43;
pub global MAX_SECRET_KEY_LENGTH: u32 = 64;
fn main(
jwt: JWT<MAX_HEADER_LENGTH, MAX_PAYLOAD_LENGTH, MAX_SIGNATURE_LENGTH>,
secret_key: SecretKey<MAX_SECRET_KEY_LENGTH>,
) -> pub bool {
jwt.verify(secret_key)
}
use noir_jwt::{constants::KEY_LIMBS_2048, JWT_RS256 as JWT, RSAPubkey};
pub global MAX_HEADER_LENGTH: u32 = 32;
pub global MAX_PAYLOAD_LENGTH: u32 = 128;
fn main(
jwt: JWT<MAX_HEADER_LENGTH, MAX_PAYLOAD_LENGTH>,
pub_key: RSAPubkey<KEY_LIMBS_2048>,
) -> pub bool {
jwt.verify(pub_key, 65537)
}
use base64::BASE64_URL_DECODER::decode_var;
use json_parser::JSON1kb;
use noir_jwt::JWT_H256 as JWT;
use noir_jwt::types::{Payload, SecretKey};
pub global MAX_HEADER_LENGTH: u32 = 64;
pub global MAX_PAYLOAD_LENGTH: u32 = 256;
pub global MAX_SIGNATURE_LENGTH: u32 = 43;
pub global MAX_SECRET_KEY_LENGTH: u32 = 64;
unconstrained fn parse_json(payload: Payload<MAX_PAYLOAD_LENGTH>) -> JSON1kb {
let mut decoded: BoundedVec<u8, (MAX_PAYLOAD_LENGTH * 3) / 4> = decode_var(payload);
for _i in decoded.len()..decoded.max_len() {
decoded.push(32); // Whitespace character
}
let json: JSON1kb = JSON1kb::parse_json(decoded.storage());
json
}
fn main(
jwt: JWT<MAX_HEADER_LENGTH, MAX_PAYLOAD_LENGTH, MAX_SIGNATURE_LENGTH>,
secret_key: SecretKey<MAX_SECRET_KEY_LENGTH>,
expected_email: BoundedVec<u8, 64>,
) -> pub bool {
let verified = jwt.verify(secret_key);
assert(verified);
/// Safety: Payload is already verified
let json: JSON1kb = unsafe { parse_json(jwt.payload) };
let email: BoundedVec<u8, 64> = json.get_string_unchecked("sub".as_bytes());
println(expected_email);
println(email);
email == expected_email
}
This library provides a set of helpers functions to generate circuit inputs. To install the library, run the following command:
npm install @zkpersona/noir-jwt
# or
yarn add @zkpersona/noir-jwt
# or
pnpm add @zkpersona/noir-jwt
# or
bun add @zkpersona/noir-jwt
Here is an example of how to use the library to generate inputs for H256 proof:
import type { InputMap } from '@noir-lang/noir_js';
import { BoundedVec, U8 } from '@zkpersona/noir-helpers';
import { JWT } from '@zkpersona/noir-jwt';
const jwt = new JWT(
'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNzQ0MzUxOTAzLCJleHAiOjE3NDQzNTkxMDN9.lKtDhCu1-ejUIEwrXPFWaaEvaXc5qZg1jUfxFxciQJ4'
);
const jwtInputs = jwt.toCircuitInputs({
maxHeaderLength: 64,
maxPayloadLength: 256,
maxSignatureLength: 43,
});
const secretKey = Uint8Array.from('secret_key');
const secretKeyArray = Array.from(secretKey).map((e) => new U8(e));
const secretKeyVec = new BoundedVec(64, () => new U8(0));
secretKeyVec.extendFromArray(secretKeyArray);
const inputs: InputMap = {
...jwtInputs,
secret_key: secretKeyVec.toJSON(),
};
And here is an example of how to use the library to generate inputs for RS256 proof:
import type { InputMap } from '@noir-lang/noir_js';
import { JWT, RSAPubKey } from '@zkpersona/noir-jwt';
const pubKeyPem = `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEArlZIVMkJ+pyjixkrQiZXYd3MwbIHUzsFwSCB/rjds1FpfgXAsArG
8EtyrKfDwZxdccLUGIlmdKO6uurL/wbcJcYxiMcJON5vPXztUAMeCNc6gvmlcsRa
5V1cdpFcjOsGwJKp/n+iIV5VOODfRdiIcE/YKb7fhCsu8xv03SNgUSi/7cwA+0nZ
6rzmQuWYYQ0VNMN0YJvFZe3iacjgKtQOlM79E6lD+s3noxp0N8kW+Aefv6lVxUD3
EbXTyxPSYR8XOrud88rsQqBrKfwz8L+IEk/dx4VVC9jXQAxvJ/NMRrOs3oitq91L
3R5k630h2aroD2sJi35ooWovymjAlLv+LwIDAQAB
-----END RSA PUBLIC KEY-----`;
const jwt = new JWT(
'eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNzQ0MzUyMTMyLCJleHAiOjE3NDQzNTkzMzJ9.e4wBkSuW7Xpx6oChK7dm9nZG4P6pqsibj54nqQ4Zrt5wAz0Ibeh5MRyv8YMV9TzUE5ta8n9P-EJuqwtPohL2uMuoPwnIxKWXt4qAOtxMkWFLEckHSYoHCXqvph-UyPLbGL2zTSJJP-SgTr7E9Y1bU5qM46oieDiQL0ao4CEk5pXjjH5ueAvyA5jfcNAr2kVYsZh8oFFs00VZcUPUXVyZsfjG7EvZ1O6jQ_nCgYqzpQL-kW9ZI_prSNtvDX4wK1JFMVqXHma7XNbvmZNKSvvWhKw-V-lfL3RgjcrVyZnTV-apaZTr6ihOEXlenyU-zOhRfwzAic9HdFG8DUR6_xVqHA',
'RS256'
);
const pubKey = RSAPubKey.fromPem(pubKeyPem);
const jwtInputs = jwt.toCircuitInputs({
maxHeaderLength: 32,
maxPayloadLength: 128,
maxSignatureLength: 18,
});
const inputs: InputMap = {
...jwtInputs,
...pubKey.toCircuitInputs(),
};
This project is licensed under the MIT License.
See the LICENSE file for details.
Contributions are welcome! Please open an issue or submit a pull request.