Skip to content

Commit

Permalink
Add device signer
Browse files Browse the repository at this point in the history
  • Loading branch information
broody committed Mar 15, 2024
1 parent f886075 commit 2bb4dff
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 79 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.1.8", fea
"abigen-rs",
] }
cairo-lang-starknet = "2.4"
coset = { version = "0.3.4", features = ["std"] }
futures = "0.3"
lazy_static = "1"
p256 = "0.13"
Expand All @@ -25,10 +26,11 @@ sha2 = "0.10"
starknet = "0.8"
starknet-crypto = "0.6"
thiserror = "1"
tokio = { version = "1", features = ["macros"] }
tokio = { version = "1", features = ["macros", "time"] }
toml = "0.8"
u256-literal = "1"
url = "2"
wasm-bindgen = "0.2"
webauthn-authenticator-rs = { version = "0.4" }
webauthn-rs-proto = "0.4"
wasm-bindgen-futures = "0.4.42"
wasm-webauthn = "0.1.0"
account-sdk = { path = "crates/account_sdk" }
13 changes: 10 additions & 3 deletions crates/account_sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ async-trait.workspace = true
base64.workspace = true
cainome.workspace = true
cairo-lang-starknet.workspace = true
coset.workspace = true
futures.workspace = true
lazy_static.workspace = true
p256.workspace = true
Expand All @@ -25,10 +26,16 @@ sha2.workspace = true
starknet.workspace = true
starknet-crypto.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true
u256-literal.workspace = true
url.workspace = true
wasm-bindgen-futures.workspace = true
wasm-bindgen.workspace = true
webauthn-authenticator-rs.workspace = true
webauthn-rs-proto.workspace = true
wasm-webauthn.workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio.workspace = true

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.69"
web-sys = "0.3.69"
6 changes: 4 additions & 2 deletions crates/account_sdk/src/tests/webauthn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use crate::abigen::account::WebauthnPubKey;
use crate::abigen::account::WebauthnSignature;
use crate::{
tests::runners::katana_runner::KatanaRunner,
webauthn_signer::{cairo_args::VerifyWebauthnSignerArgs, P256r1Signer},
webauthn_signer::{
cairo_args::VerifyWebauthnSignerArgs, signers::p256r1::P256r1Signer, signers::Signer,
},
};

#[tokio::test]
Expand Down Expand Up @@ -63,7 +65,7 @@ async fn test_verify_webauthn_explicit() {

let challenge = felt!("0x0169af1f6f99d35e0b80e0140235ec4a2041048868071a8654576223934726f5");
let challenge_bytes = challenge.to_bytes_be().to_vec();
let response = data.signer.sign(&challenge_bytes);
let response = data.signer.sign(&challenge_bytes).await;

let args = VerifyWebauthnSignerArgs::from_response(origin, challenge_bytes, response.clone());

Expand Down
3 changes: 2 additions & 1 deletion crates/account_sdk/src/tests/webauthn/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ use crate::{
deploy_contract::FEE_TOKEN_ADDRESS,
transaction_waiter::TransactionWaiter,
webauthn_signer::account::WebauthnAccount,
webauthn_signer::signers::Signer,
};
use crate::{
abigen::erc20::{Erc20Contract, U256},
webauthn_signer::cairo_args::pub_key_to_felts,
};
use crate::{
deploy_contract::single_owner_account, tests::runners::TestnetRunner,
webauthn_signer::P256r1Signer,
webauthn_signer::signers::p256r1::P256r1Signer,
};

use super::super::deployment_test::{declare, deploy};
Expand Down
5 changes: 3 additions & 2 deletions crates/account_sdk/src/webauthn_signer/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ use std::sync::Arc;

use crate::felt_ser::to_felts;

use super::{cairo_args::VerifyWebauthnSignerArgs, P256r1Signer};
use super::cairo_args::VerifyWebauthnSignerArgs;
use crate::abigen::account::WebauthnSignature;
use crate::webauthn_signer::signers::{p256r1::P256r1Signer, Signer};

pub struct WebauthnAccount<P>
where
Expand Down Expand Up @@ -112,7 +113,7 @@ where
) -> Result<Vec<FieldElement>, Self::SignError> {
let tx_hash = execution.transaction_hash(self.chain_id, self.address, query_only, self);
let challenge = tx_hash.to_bytes_be().to_vec();
let assertion = self.signer.sign(&challenge);
let assertion = self.signer.sign(&challenge).await;

let args =
VerifyWebauthnSignerArgs::from_response(self.origin.clone(), challenge, assertion);
Expand Down
69 changes: 1 addition & 68 deletions crates/account_sdk/src/webauthn_signer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,79 +1,12 @@
use p256::{
ecdsa::{signature::Signer, Signature, SigningKey, VerifyingKey},
elliptic_curve::sec1::Coordinates,
};
use rand_core::OsRng;
use starknet::{core::types::FieldElement, macros::felt};

use crate::webauthn_signer::credential::{AuthenticatorData, CliendData};

use self::credential::AuthenticatorAssertionResponse;

pub mod account;
pub mod cairo_args;
pub mod credential;
pub mod signers;

pub type U256 = (FieldElement, FieldElement);
pub type Secp256r1Point = (U256, U256);

// "Webauthn v1"
pub const WEBAUTHN_SIGNATURE_TYPE: FieldElement = felt!("0x576562617574686e207631");

#[derive(Debug, Clone)]
pub struct P256r1Signer {
pub signing_key: SigningKey,
rp_id: String,
}

impl P256r1Signer {
pub fn random(rp_id: String) -> Self {
let signing_key = SigningKey::random(&mut OsRng);
Self::new(signing_key, rp_id)
}
pub fn new(signing_key: SigningKey, rp_id: String) -> Self {
Self { signing_key, rp_id }
}
pub fn public_key_bytes(&self) -> ([u8; 32], [u8; 32]) {
let verifying_key: VerifyingKey = VerifyingKey::from(&self.signing_key);
let encoded = &verifying_key.to_encoded_point(false);
let (x, y) = match encoded.coordinates() {
Coordinates::Uncompressed { x, y } => (x, y),
_ => panic!("unexpected compression"),
};
(
x.as_slice().try_into().unwrap(),
y.as_slice().try_into().unwrap(),
)
}
pub fn sign(&self, challenge: &[u8]) -> AuthenticatorAssertionResponse {
use sha2::{digest::Update, Digest, Sha256};

let authenticator_data = AuthenticatorData {
rp_id_hash: [0; 32],
flags: 0b00000101,
sign_count: 0,
};
let client_data_json = CliendData::new(challenge, self.rp_id.clone()).to_json();
let client_data_hash = Sha256::new().chain(client_data_json.clone()).finalize();

let mut to_sign = Into::<Vec<u8>>::into(authenticator_data.clone());
to_sign.append(&mut client_data_hash.to_vec());
let signature: Signature = self.signing_key.try_sign(&to_sign).unwrap();
let signature = signature.to_bytes().to_vec();

AuthenticatorAssertionResponse {
authenticator_data,
client_data_json,
signature,
user_handle: None,
}
}
}

#[test]
fn test_signer() {
let rp_id = "https://localhost:8080".to_string();
let signer = P256r1Signer::random(rp_id);
let calldata = signer.sign("842903840923".as_bytes());
dbg!(&calldata);
}
76 changes: 76 additions & 0 deletions crates/account_sdk/src/webauthn_signer/signers/device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use async_trait::async_trait;
use coset::CoseKey;
use futures::channel::oneshot;
use wasm_bindgen_futures::spawn_local;
use wasm_webauthn::*;

use crate::webauthn_signer::credential::{self, AuthenticatorAssertionResponse, AuthenticatorData};

use super::Signer;

#[derive(Debug, Clone)]
pub struct DeviceSigner {
rp_id: String,
credential_id: Vec<u8>,
pub_key: CoseKey,
}

impl DeviceSigner {
pub fn new(rp_id: String, credential_id: Vec<u8>, pub_key: CoseKey) -> Self {
Self {
rp_id,
credential_id,
pub_key,
}
}
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Signer for DeviceSigner {
async fn sign(&self, challenge: &[u8]) -> AuthenticatorAssertionResponse {
let (sender, receiver) = oneshot::channel();

let mut credential = Credential::from(CredentialID(self.credential_id.clone()));
credential.public_key = Some(self.pub_key.clone());

let rp_id = self.rp_id.to_owned();
let challenge = challenge.to_vec();

spawn_local(async move {
let results = GetAssertionArgsBuilder::default()
.rp_id(Some(rp_id))
.credentials(Some(vec![credential]))
.challenge(challenge.to_vec())
.build()
.expect("invalid args")
.get_assertion()
.await
.expect("get assertion");

sender.send(results).expect("receiver dropped");
});

let GetAssertionResponse {
signature,
client_data_json,
flags,
counter,
} = receiver.await.expect("receiver dropped");

AuthenticatorAssertionResponse {
authenticator_data: AuthenticatorData {
rp_id_hash: [0; 32],
flags,
sign_count: counter,
},
client_data_json,
signature,
user_handle: None,
}
}

fn public_key_bytes(&self) -> ([u8; 32], [u8; 32]) {
unimplemented!("unimplemented public_key_bytes")
}
}
12 changes: 12 additions & 0 deletions crates/account_sdk/src/webauthn_signer/signers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use super::credential::AuthenticatorAssertionResponse;
use async_trait::async_trait;

pub mod device;
pub mod p256r1;

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait Signer {
async fn sign(&self, challenge: &[u8]) -> AuthenticatorAssertionResponse;
fn public_key_bytes(&self) -> ([u8; 32], [u8; 32]);
}
89 changes: 89 additions & 0 deletions crates/account_sdk/src/webauthn_signer/signers/p256r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::webauthn_signer::credential::{AuthenticatorData, CliendData};
use async_trait::async_trait;
use p256::{
ecdsa::{signature::Signer as P256Signer, Signature, SigningKey, VerifyingKey},
elliptic_curve::sec1::Coordinates,
};
use rand_core::OsRng;

use crate::webauthn_signer::credential::AuthenticatorAssertionResponse;

use super::Signer;

#[derive(Debug, Clone)]
pub struct P256r1Signer {
pub signing_key: SigningKey,
rp_id: String,
}

impl P256r1Signer {
pub fn new(rp_id: String, signing_key: SigningKey) -> Self {
Self { rp_id, signing_key }
}

pub fn random(rp_id: String) -> Self {
let signing_key = SigningKey::random(&mut OsRng);
Self::new(rp_id, signing_key)
}
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Signer for P256r1Signer {
fn public_key_bytes(&self) -> ([u8; 32], [u8; 32]) {
P256VerifyingKeyConverter::new(*self.signing_key.verifying_key()).to_bytes()
}

async fn sign(&self, challenge: &[u8]) -> AuthenticatorAssertionResponse {
use sha2::{digest::Update, Digest, Sha256};

let authenticator_data = AuthenticatorData {
rp_id_hash: [0; 32],
flags: 0b00000101,
sign_count: 0,
};
let client_data_json = CliendData::new(challenge, self.rp_id.clone()).to_json();
let client_data_hash = Sha256::new().chain(client_data_json.clone()).finalize();

let mut to_sign = Into::<Vec<u8>>::into(authenticator_data.clone());
to_sign.append(&mut client_data_hash.to_vec());
let signature: Signature = self.signing_key.try_sign(&to_sign).unwrap();
let signature = signature.to_bytes().to_vec();

AuthenticatorAssertionResponse {
authenticator_data,
client_data_json,
signature,
user_handle: None,
}
}
}

pub struct P256VerifyingKeyConverter {
pub verifying_key: VerifyingKey,
}

impl P256VerifyingKeyConverter {
pub fn new(verifying_key: VerifyingKey) -> Self {
Self { verifying_key }
}
pub fn to_bytes(&self) -> ([u8; 32], [u8; 32]) {
let encoded = &self.verifying_key.to_encoded_point(false);
let (x, y) = match encoded.coordinates() {
Coordinates::Uncompressed { x, y } => (x, y),
_ => panic!("unexpected compression"),
};
(
x.as_slice().try_into().unwrap(),
y.as_slice().try_into().unwrap(),
)
}
}

#[tokio::test]
async fn test_signer() {
let rp_id = "https://localhost:8080".to_string();
let signer = P256r1Signer::random(rp_id);
let calldata = signer.sign("842903840923".as_bytes()).await;
dbg!(&calldata);
}

0 comments on commit 2bb4dff

Please sign in to comment.