Skip to content

Commit

Permalink
feat: webauthn
Browse files Browse the repository at this point in the history
  • Loading branch information
olehmisar committed Oct 24, 2024
1 parent 2d2850b commit e2acc34
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 44 deletions.
21 changes: 0 additions & 21 deletions .github/workflows/release.yml

This file was deleted.

8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: Noir tests

on:
push:
branches:
- master
branches:
- master
pull_request:

env:
Expand All @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [nightly, 0.34.0]
toolchain: [nightly, 0.35.0]
steps:
- name: Checkout sources
uses: actions/checkout@v4
Expand All @@ -38,7 +38,7 @@ jobs:
- name: Install Nargo
uses: noir-lang/[email protected]
with:
toolchain: 0.34.0
toolchain: 0.35.0

- name: Run formatter
run: nargo fmt --check
5 changes: 3 additions & 2 deletions Nargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[package]
name = "noir_library"
name = "webauthn"
type = "lib"
authors = [""]
authors = ["Oleh Misarosh <[email protected]"]
compiler_version = ">=0.34.0"

[dependencies]
base64 = { git = "https://github.com/olehmisar/noir_base64/", tag = "v0.3.0" }
43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# noir-library-starter
<!-- # noir-library-starter
This repository is a template used by the noir-lang org when creating internally maintained libraries.
Expand All @@ -10,28 +10,43 @@ This provides out of the box:
Feel free to use this template as a starting point to create your own Noir libraries.
---
--- -->

# LIBRARY_NAME

Add a brief description of the library

## Benchmarks

TODO
# Noir WebAuthn

Provides `webauthn::verify_signature` function that verifies a WebAuthn signature(also called passkey signature).

## Installation

In your _Nargo.toml_ file, add the version of this library you would like to install under dependency:

```
```toml
[dependencies]
LIBRARY = { tag = "v0.1.0", git = "https://github.com/noir-lang/LIBRARY_NAME" }
webauthn = { tag = "v0.1.0", git = "https://github.com/olehmisar/noir_webauthn" }
```

## `library`
## Usage

```rs
let result = webauthn::verify_signature(
// [u8; 32] - x coordinate of WebAuthn public key generated by `credentials.create`
public_key_x,
// [u8; 32] - y coordinate of WebAuthn public key generated by `credentials.create`
public_key_y,
// [u8; 64] - signature generated by `credentials.get`
signature,
// BoundedVec<u8, 256> - clientDataJSON generated `credentials.get`
client_data_json,
// BoundedVec<u8, 64> - authenticatorData generated `credentials.get`
authenticator_data,
// [u8; 32] - challenge generated `credentials.get`
challenge,
// u32 - index of challenge in clientDataJSON
challenge_index,
);
assert(result, "webauthn signature verification failed");
```

### Usage
## Benchmarks

`PLACEHOLDER`
TODO
154 changes: 151 additions & 3 deletions src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,153 @@
/// This doesn't really do anything by ensures that there is a test for CI to run.
// TODO(security): what is the correct size for client_data_json and authenticator_data? Is it even fixed?
// if the authenticator data or client data json generated by the browser are too long, the signature verification will fail (rendering the account unusable and funds lost)

// TODO(security): determine the correct max length. Reference: https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data#data_structure
global AUTHENTICATOR_DATA_MAX_LEN: u32 = 64; // usually around 37
// TODO(security): determine the correct max length. Reference: https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorResponse/clientDataJSON#value
global CLIENT_DATA_JSON_MAX_LEN: u32 = 256; // usually around 134

global SIGNATURE_LEN: u32 = 64;

pub fn verify_signature(
public_key_x: [u8; 32],
public_key_y: [u8; 32],
signature: [u8; SIGNATURE_LEN],
client_data_json: BoundedVec<u8, CLIENT_DATA_JSON_MAX_LEN>,
authenticator_data: BoundedVec<u8, AUTHENTICATOR_DATA_MAX_LEN>,
challenge: [u8; 32],
challenge_index: u32
) -> bool {
let client_data_json_hash: [u8; 32] = std::hash::sha256_var(client_data_json.storage(), client_data_json.len() as u64);
let concatenated: BoundedVec<u8, AUTHENTICATOR_DATA_MAX_LEN + 32> = concat(authenticator_data, client_data_json_hash);
let hashed_message = std::hash::sha256_var(concatenated.storage(), concatenated.len() as u64);

let challenge_base64_url: [_; 40] = base64::BASE64_ENCODER_URL_SAFE_NO_PAD.encode(challenge);
str_contains_base64_url(
client_data_json.storage(),
challenge_base64_url,
challenge_index
);

let verification = std::ecdsa_secp256r1::verify_signature(public_key_x, public_key_y, signature, hashed_message);
verification
}

fn str_contains_base64_url<let N: u32, let H: u32>(haystack: [u8; H], needle: [u8; N], index: u32) {
for i in 0..needle.len() {
if (needle[i] != 61) { // ignore padding (61 is '=')
assert(haystack[index + i] == needle[i]);
}
}
}

#[test]
fn my_test() {
assert(
verify_signature(
[
91, 200, 36, 72, 14, 85, 105, 189, 204, 19, 185, 157, 161, 241, 56, 107, 228, 8, 67, 156, 7, 183, 173, 111, 146, 216, 51, 2, 244, 251, 78, 203
],
[
30, 52, 243, 79, 92, 114, 5, 253, 138, 212, 14, 51, 122, 247, 225, 82, 193, 243, 157, 70, 225, 62, 254, 206, 247, 110, 252, 111, 188, 128, 142, 226
],
[
47, 222, 74, 22, 26, 2, 142, 123, 25, 179, 68, 61, 58, 204, 200, 245, 241, 176, 227, 237, 173, 115, 147, 229, 128, 165, 63, 170, 148, 250, 171, 141, 115, 50, 249, 181, 84, 62, 116, 119, 139, 101, 89, 14, 140, 246, 186, 29, 143, 146, 13, 198, 186, 85, 47, 213, 235, 176, 236, 26, 88, 231, 191, 129
],
BoundedVec::from(
[
123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110, 46, 103, 101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34, 58, 34, 85, 76, 76, 69, 80, 57, 79, 82, 66, 114, 114, 55, 117, 103, 50, 106, 84, 56, 81, 119, 52, 102, 107, 101, 80, 74, 98, 113, 75, 115, 55, 105, 118, 68, 81, 82, 110, 53, 75, 122, 100, 49, 65, 34, 44, 34, 111, 114, 105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 53, 49, 56, 52, 34, 44, 34, 99, 114, 111, 115, 115, 79, 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125
]
),
BoundedVec::from(
[
73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 29, 0, 0, 0, 0
]
),
[
80, 178, 196, 63, 211, 145, 6, 186, 251, 186, 13, 163, 79, 196, 48, 225, 249, 30, 60, 150, 234, 42, 206, 226, 188, 52, 17, 159, 146, 179, 119, 80
],
36
)
);
}

#[test]
fn smoke_test() {
assert(true);
fn test_fail() {
assert(
!verify_signature(
[
90, 200, 36, 72, 14, 85, 105, 189, 204, 19, 185, 157, 161, 241, 56, 107, 228, 8, 67, 156, 7, 183, 173, 111, 146, 216, 51, 2, 244, 251, 78, 203
],
[
30, 52, 243, 79, 92, 114, 5, 253, 138, 212, 14, 51, 122, 247, 225, 82, 193, 243, 157, 70, 225, 62, 254, 206, 247, 110, 252, 111, 188, 128, 142, 226
],
[
47, 222, 74, 22, 26, 2, 142, 123, 25, 179, 68, 61, 58, 204, 200, 245, 241, 176, 227, 237, 173, 115, 147, 229, 128, 165, 63, 170, 148, 250, 171, 141, 115, 50, 249, 181, 84, 62, 116, 119, 139, 101, 89, 14, 140, 246, 186, 29, 143, 146, 13, 198, 186, 85, 47, 213, 235, 176, 236, 26, 88, 231, 191, 129
],
BoundedVec::from(
[
123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110, 46, 103, 101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34, 58, 34, 85, 76, 76, 69, 80, 57, 79, 82, 66, 114, 114, 55, 117, 103, 50, 106, 84, 56, 81, 119, 52, 102, 107, 101, 80, 74, 98, 113, 75, 115, 55, 105, 118, 68, 81, 82, 110, 53, 75, 122, 100, 49, 65, 34, 44, 34, 111, 114, 105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 53, 49, 56, 52, 34, 44, 34, 99, 114, 111, 115, 115, 79, 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125
]
),
BoundedVec::from(
[
73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 29, 0, 0, 0, 0
]
),
[
80, 178, 196, 63, 211, 145, 6, 186, 251, 186, 13, 163, 79, 196, 48, 225, 249, 30, 60, 150, 234, 42, 206, 226, 188, 52, 17, 159, 146, 179, 119, 80
],
36
)
);
}

#[test]
fn some_test() {
assert(
verify_signature(
[
139, 86, 118, 230, 15, 13, 30, 204, 6, 133, 248, 82, 54, 101, 178, 189, 126, 170, 84, 48, 0, 106, 149, 40, 133, 99, 30, 73, 2, 210, 205, 200
],
[
184, 8, 42, 248, 97, 207, 125, 175, 201, 50, 10, 102, 148, 60, 53, 169, 208, 70, 112, 255, 179, 218, 110, 33, 33, 135, 156, 9, 30, 230, 17, 26
],
[
182, 153, 194, 137, 148, 109, 190, 71, 178, 33, 99, 179, 75, 12, 9, 132, 225, 154, 15, 237, 58, 248, 132, 130, 94, 16, 155, 206, 77, 21, 66, 223, 79, 248, 19, 205, 57, 183, 65, 109, 45, 135, 165, 109, 50, 56, 84, 208, 76, 252, 111, 240, 6, 114, 169, 202, 193, 130, 20, 17, 144, 51, 55, 254
],
BoundedVec {
storage: [
123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110, 46, 103, 101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34, 58, 34, 67, 70, 99, 69, 97, 110, 104, 100, 102, 45, 57, 74, 106, 84, 51, 48, 89, 100, 116, 98, 78, 87, 54, 53, 81, 117, 88, 52, 85, 84, 80, 57, 79, 118, 122, 66, 85, 97, 74, 95, 87, 111, 111, 34, 44, 34, 111, 114, 105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 53, 49, 56, 52, 34, 44, 34, 99, 114, 111, 115, 115, 79, 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125, 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, 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
],
len: 134
},
BoundedVec {
storage: [
73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 29, 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
] ,
len: 37
},
[
8, 87, 4, 106, 120, 93, 127, 239, 73, 141, 61, 244, 97, 219, 91, 53, 110, 185, 66, 229, 248, 81, 51, 253, 58, 252, 193, 81, 162, 127, 90, 138
],
36
)
);
}

fn concat<let N: u32, let S: u32>(a: BoundedVec<u8, N>, b: [u8; 32]) -> BoundedVec<u8, S> {
assert_eq(N + 32, S, "combined bounded vec length does not match return bounded vec length");
let mut result = [0 as u8; S];
let mut j = 0;
for i in 0..N {
if (i < a.len()) {
result[j] = a.get(i);
j += 1;
}
}
for i in 0..b.len() {
result[j] = b[i];
j += 1;
}
BoundedVec { storage: result, len: j }
}

0 comments on commit e2acc34

Please sign in to comment.