Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oracle: finalize claim verification implementation #68

Merged
merged 3 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Deprecated:
- `registry-unstable.i-am-human.testnet` @ registry/v1.2.0
- **Demo SBT Issuer**: `sbt1.i-am-human.testnet` (the `demo_issuer` contract)
- **Fractal Issuer**: `i-am-human-staging.testnet` (the `oracle` contract).
registry: `registry-1.i-am-human.testnet`; Verification pubkey base64: `zqMwV9fTRoBOLXwt1mHxBAF3d0Rh9E9xwSAXR3/KL5E=`, `claim_ttl`: 3600ms, FV class: 1
registry: `registry-1.i-am-human.testnet`; Verification pubkey base64: `FGoAI6DXghOSK2ZaKVT/5lSP4X4JkoQQphv1FD4YRto=`, `claim_ttl`: 3600ms, FV class: 1
- **Community-SBT**: `community-v1.i-am-human.testnet` @ community-sbt/v4.1.0
registry: `registry-1.i-am-human.testnet`; classes: OG=1, Vibes=2
Max and default [TTL](./contracts/community-sbt/README.md#ttl) = 1year.
Expand Down
1 change: 0 additions & 1 deletion contracts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions contracts/oracle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ crate-type = ["cdylib", "rlib"]
uint.workspace = true
near-sdk.workspace = true
serde_json.workspace = true
ed25519-dalek.workspace = true

cost = { path = "../cost" }
sbt = { path = "../sbt" }

[dev-dependencies]
rand = "^0.7"
near-primitives.workspace = true
near-crypto.workspace = true
ed25519-dalek.workspace = true

# integration tests
pretty_assertions.workspace = true
anyhow.workspace = true
tokio.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion contracts/oracle/src/checks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(all(test, not(target_arch = "wasm32")))]
pub mod tests {

use crate::*;

pub fn deserialize_claim(claim_b64: &str) -> Claim {
let c_bz = crate::b64_decode("claim", claim_b64.to_string()).unwrap();
let c = Claim::try_from_slice(&c_bz).unwrap();
Expand Down
165 changes: 13 additions & 152 deletions contracts/oracle/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use ed25519_dalek::{PUBLIC_KEY_LENGTH};
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::{LazyOption, UnorderedSet};
use near_sdk::serde::Serialize;
Expand Down Expand Up @@ -45,7 +44,7 @@ pub struct Contract {
/// SBT ttl until expire in miliseconds (expire=issue_time+sbt_ttl)
pub sbt_ttl_ms: u64,
/// ed25519 pub key (could be same as a NEAR pub key)
pub authority_pubkey: [u8; PUBLIC_KEY_LENGTH], // Vec<u8>,
pub authority_pubkey: [u8; PUBLIC_KEY_LEN], // Vec<u8>,
pub used_identities: UnorderedSet<Vec<u8>>,

/// used for backend key rotation
Expand Down Expand Up @@ -111,7 +110,8 @@ impl Contract {
**********/

/// Mints a new SBT for the transaction signer.
/// @claim_b64: standard base64 borsh serialized Claim (same bytes as used for the claim signature)
/// @claim_b64: standard base64 borsh serialized Claim (same bytes as used for the claim signature).
/// @claim_sig: standard base64 serialized ed25519 signature.
/// If `metadata.expires_at` is None then we set it to ` now+self.ttl`.
/// Panics if `metadata.expires_at > now+self.ttl`.
// TODO: update result to return TokenId
Expand All @@ -133,9 +133,8 @@ impl Contract {
let claim_bytes = b64_decode("claim_b64", claim_b64)?;
let claim = Claim::try_from_slice(&claim_bytes)
.map_err(|_| CtrError::Borsh("claim".to_string()))?;
let signature = b64_decode("sign_b64", claim_sig)?;
let signature: [u8; 64] = signature.try_into().expect("signature must be 64 bytes");
verify_claim(&self.authority_pubkey, claim_bytes, &signature)?;
let signature = b64_decode("claim_sig", claim_sig)?;
verify_claim(&signature, &claim_bytes, &self.authority_pubkey)?;

if claim.verified_kyc {
require!(
Expand Down Expand Up @@ -254,7 +253,7 @@ impl Contract {

/* for testing the callback
#[payable]
pub fn admin_mint(&mut self, receipient: AccountId, external_id: String) -> Promise {
pub fn admin_mint(&mut self, recipient: AccountId, external_id: String) -> Promise {
let external_id = normalize_external_id(external_id).ok().unwrap();
let now = env::block_timestamp_ms();
let metadata = TokenMetadata {
Expand All @@ -267,7 +266,7 @@ impl Contract {
ext_registry::ext(self.registry.clone())
.with_attached_deposit(MINT_COST)
.with_static_gas(MINT_GAS)
.sbt_mint(vec![(receipient, vec![metadata])])
.sbt_mint(vec![(recipient, vec![metadata])])
.then(
Self::ext(env::current_account_id())
.with_static_gas(Gas::ONE_TERA * 3)
Expand Down Expand Up @@ -299,45 +298,6 @@ impl Contract {
// - fn sbt_renew
}

mod sys {
extern "C" {
pub fn ed25519_verify(
sig_len: u64,
sig_ptr: u64,
msg_len: u64,
msg_ptr: u64,
pub_key_len: u64,
pub_key_ptr: u64,
) -> u64;
}
}

pub fn ed25519_verify(signature: &[u8; 64], message: &[u8], public_key: &[u8; 32]) -> bool {
unsafe {
sys::ed25519_verify(
signature.len() as _,
signature.as_ptr() as _,
message.len() as _,
message.as_ptr() as _,
public_key.len() as _,
public_key.as_ptr() as _,
) == 1
}
}

fn verify_claim(
pubkey: &[u8; PUBLIC_KEY_LENGTH],
claim: Vec<u8>,
claim_sig: &[u8; 64],
) -> Result<(), CtrError> {
let valid = ed25519_verify(claim_sig, &claim, pubkey);
if !valid {
return Err(CtrError::Signature("invalid signature".to_string()))
} else {
Ok(())
}
}

#[near_bindgen]
impl SBTContract for Contract {
fn sbt_metadata(&self) -> ContractMetadata {
Expand All @@ -359,24 +319,13 @@ pub enum CallbackResult<T, E> {
mod checks;

#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
extern crate ed25519_dalek;
extern crate rand;

pub mod tests {
use crate::*;
use ed25519_dalek::Keypair;
use near_sdk::test_utils::VMContextBuilder;
use near_sdk::{testing_env, VMContext};

use ed25519_dalek::{Keypair, Signer};
use rand::rngs::OsRng;

fn b64_encode(data: Vec<u8>) -> String {
near_sdk::base64::encode(data)
}

fn acc_claimer() -> AccountId {
"user1.near".parse().unwrap()
}
use crate::util::tests::{acc_claimer, b64_encode, gen_key, mk_claim_sign};

fn acc_u1() -> AccountId {
"user2.near".parse().unwrap()
Expand Down Expand Up @@ -420,9 +369,7 @@ mod tests {
.is_view(false)
.build();

let mut csprng = OsRng {};
let keypair: Keypair = Keypair::generate(&mut csprng);

let keypair = gen_key();
let ctr = Contract::new(
b64_encode(keypair.public.to_bytes().to_vec()),
ContractMetadata {
Expand All @@ -443,35 +390,6 @@ mod tests {
return (ctx, ctr, keypair);
}

/// @timestamp: in seconds
pub fn mk_claim(timestamp: u64, external_id: &str, is_verified_kyc: bool) -> Claim {
Claim {
claimer: acc_claimer(),
external_id: external_id.to_string(),
timestamp,
verified_kyc: is_verified_kyc,
}
}

// returns b64 serialized claim and signature
fn sign_claim(c: &Claim, k: &Keypair) -> (String, String) {
let c_bz = c.try_to_vec().unwrap();
let sig = k.sign(&c_bz);
let sig_bz = sig.to_bytes();
(b64_encode(c_bz), b64_encode(sig_bz.to_vec()))
}

fn mk_claim_sign(
timestamp: u64,
external_id: &str,
k: &Keypair,
is_verified_kyc: bool,
) -> (Claim, String, String) {
let c = mk_claim(timestamp, external_id, is_verified_kyc);
let (c_str, sig) = sign_claim(&c, &k);
return (c, c_str, sig);
}

fn assert_bad_request(resp: Result<Promise, CtrError>, expected_msg: &str) {
match resp {
Err(CtrError::BadRequest(s)) => {
Expand Down Expand Up @@ -586,9 +504,8 @@ mod tests {
let claim_sig_b64 = "38X2TnWgc6moc4zReAJFQ7BjtOUlWZ+i3YQl9gSMOXwnm5gupfHV/YGmGPOek6SSkotT586d4zTTT2U8Qh3GBw==".to_owned();

let claim_bytes = b64_decode("claim_b64", claim_b64.clone()).unwrap();
let signature = b64_decode("sign_b64", claim_sig_b64.clone()).unwrap();
let signature: [u8; 64] = signature.try_into().expect("signature must be 64 bytes");
verify_claim(&ctr.authority_pubkey, claim_bytes, &signature).unwrap();
let signature = b64_decode("sig_b64", claim_sig_b64.clone()).unwrap();
verify_claim(&signature, &claim_bytes, &ctr.authority_pubkey).unwrap();

let r = ctr.sbt_mint(claim_b64, claim_sig_b64, None);
match r {
Expand Down Expand Up @@ -659,60 +576,4 @@ mod tests {
Ok(_) => panic!("expected DuplicatedID, got: Ok"),
}
}

#[test]
fn pubkey() {
let pk_bytes = pubkey_from_b64("kSj7W/TdN9RGLgdJA8ac7i/WdQdm2lwQ1IPGlO1L3xc=".to_string());
assert_ne!(pk_bytes[0], 0);

let mut csprng = OsRng {};
let k = Keypair::generate(&mut csprng);

let (_, c_str, sig) = mk_claim_sign(start() / SECOND, "0x12", &k, false);
let claim_bytes = b64_decode("claim_b64", c_str).unwrap();
let signature = b64_decode("sign_b64", sig).unwrap();
let signature: [u8; 64] = signature.try_into().expect("signature must be 64 bytes");
let res = verify_claim(
&k.public.to_bytes(),
claim_bytes,
&signature,
);
assert!(res.is_ok(), "verification result: {:?}", res);
}

#[test]
fn pubkey_near_crypto() {
//let sk = near_crypto::SecretKey::from_str("ed25519:...").unwrap();
let sk = near_crypto::SecretKey::from_random(near_crypto::KeyType::ED25519);
let k = match sk.clone() {
near_crypto::SecretKey::ED25519(k) => ed25519_dalek::Keypair::from_bytes(&k.0).unwrap(),
_ => panic!("expecting ed25519 key"),
};

let pk_bs58 = near_sdk::bs58::encode(k.public).into_string();
let pk_b64 = b64_encode(k.public.as_bytes().to_vec());
let sk_str = near_sdk::bs58::encode(k.secret).into_string();
let sk_str2 = sk.to_string();
println!(
"pubkey_bs58={} pubkey_b64={}\nsecret={} {}",
pk_bs58, pk_b64, sk_str, sk_str2,
);

// let sk2 = near_crypto::SecretKey::from_str(
// "secp256k1:AxynSCWRr2RrBXbzcbykYTo5vPmCkMf35s1D1bXV8P51",
// )
// .unwrap();
// println!("\nsecp: {}, public: {}", sk2, sk2.public_key());

// assert!(false);
}

#[test]
fn claim_serialization() {
let c = mk_claim(1677621259142, "some_111#$!", false);
let claim_bz = c.try_to_vec().unwrap();
let claim_str = b64_encode(claim_bz);
let claim2 = checks::tests::deserialize_claim(&claim_str);
assert_eq!(c, claim2, "serialization should work");
}
}
Loading