Skip to content

Commit

Permalink
Change advanced-auth example to be built on increment example (stella…
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmcculloch authored Oct 9, 2022
1 parent 71c07f0 commit 73e258a
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 135 deletions.
135 changes: 68 additions & 67 deletions auth_advanced/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,90 +1,91 @@
#![no_std]

#[cfg(feature = "testutils")]
extern crate std;

mod test;

use soroban_auth::{
verify, {Identifier, Signature},
};
use soroban_sdk::{contractimpl, contracttype, symbol, BigInt, Env};
use soroban_sdk::{contracterror, contractimpl, contracttype, panic_error, symbol, BigInt, Env};

#[contracttype]
pub enum DataKey {
SavedNum(Identifier),
Counter(Identifier),
Nonce(Identifier),
Admin,
}

fn read_nonce(e: &Env, id: &Identifier) -> BigInt {
let key = DataKey::Nonce(id.clone());
e.data()
.get(key)
.unwrap_or_else(|| Ok(BigInt::zero(e)))
.unwrap()
#[contracterror]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Error {
IncorrectNonceForInvoker = 1,
IncorrectNonce = 2,
}

fn verify_and_consume_nonce(e: &Env, auth: &Signature, expected_nonce: &BigInt) {
match auth {
Signature::Invoker => {
if BigInt::zero(&e) != expected_nonce {
panic!("nonce should be zero for Invoker")
}
return;
}
_ => {}
}

let id = auth.identifier(&e);
let key = DataKey::Nonce(id.clone());
let nonce = read_nonce(e, &id);

if nonce != expected_nonce {
panic!("incorrect nonce")
}
e.data().set(key, &nonce + 1);
}

pub struct ExampleContract;
pub struct IncrementContract;

#[contractimpl]
impl ExampleContract {
/// Set the admin identifier. May be called only once.
pub fn set_admin(e: Env, admin: Identifier) {
if e.data().has(DataKey::Admin) {
panic!("admin is already set")
}

e.data().set(DataKey::Admin, admin);
impl IncrementContract {
/// Increment increments a counter for the invoker, and returns the value.
pub fn increment(env: Env, sig: Signature, nonce: BigInt) -> u32 {
// Verify that the signature signs and authorizes this invocation.
let id = sig.identifier(&env);
verify(&env, &sig, symbol!("increment"), (&id, &nonce));

// Verify that the nonce has not been consumed to prevent replay of the
// same presigned invocation more than once.
verify_and_consume_nonce(&env, &sig, &nonce);

// Construct a key for the data being stored. Use an enum to set the
// contract up well for adding other types of data to be stored.
let key = DataKey::Counter(id);

// Get the current count for the invoker.
let mut count: u32 = env
.data()
.get(&key)
.unwrap_or(Ok(0)) // If no value set, assume 0.
.unwrap(); // Panic if the value of COUNTER is not u32.

// Increment the count.
count += 1;

// Save the count.
env.data().set(&key, count);

// Return the count to the caller.
count
}

/// Save the number for an authenticated [Identifier].
pub fn save_num(e: Env, sig: Signature, nonce: BigInt, num: BigInt) {
verify_and_consume_nonce(&e, &sig, &nonce);

let auth_id = sig.identifier(&e);

verify(&e, &sig, symbol!("save_num"), (&auth_id, nonce, &num));

e.data().set(DataKey::SavedNum(auth_id), num);
pub fn nonce(env: Env, id: Identifier) -> BigInt {
get_nonce(&env, &id)
}
}

// The admin can write data for any Identifier
pub fn overwrite(e: Env, sig: Signature, nonce: BigInt, id: Identifier, num: BigInt) {
let auth_id = sig.identifier(&e);
if auth_id != e.data().get_unchecked(DataKey::Admin).unwrap() {
panic!("not authorized by admin")
fn verify_and_consume_nonce(env: &Env, auth: &Signature, nonce: &BigInt) {
match auth {
Signature::Invoker => {
if BigInt::zero(env) != nonce {
panic_error!(env, Error::IncorrectNonceForInvoker);
}
}
Signature::Ed25519(_) | Signature::Account(_) => {
let id = auth.identifier(env);
if nonce != &get_nonce(env, &id) {
panic_error!(env, Error::IncorrectNonce);
}
set_nonce(env, &id, nonce + 1);
}

verify_and_consume_nonce(&e, &sig, &nonce);

verify(&e, &sig, symbol!("overwrite"), (auth_id, nonce, &id, &num));

e.data().set(DataKey::SavedNum(id), num);
}
}

pub fn nonce(e: Env, id: Identifier) -> BigInt {
read_nonce(&e, &id)
}
fn get_nonce(env: &Env, id: &Identifier) -> BigInt {
let key = DataKey::Nonce(id.clone());
env.data()
.get(key)
.unwrap_or_else(|| Ok(BigInt::zero(env)))
.unwrap()
}

fn set_nonce(env: &Env, id: &Identifier, nonce: BigInt) {
let key = DataKey::Nonce(id.clone());
env.data().set(key, nonce);
}

mod test;
172 changes: 104 additions & 68 deletions auth_advanced/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,99 +1,135 @@
#![cfg(test)]

use super::*;
use ed25519_dalek::Keypair;
use rand::thread_rng;
use soroban_sdk::testutils::ed25519::Sign;
use soroban_sdk::{testutils::Accounts, BytesN, Env};

use soroban_auth::{Ed25519Signature, SignaturePayload, SignaturePayloadV0};
use soroban_sdk::{BytesN, Env, IntoVal, RawVal, Symbol, Vec};

fn generate_keypair() -> Keypair {
Keypair::generate(&mut thread_rng())
}
#[test]
fn test_auth_with_invoker() {
let env = Env::default();
let contract_id = env.register_contract(None, IncrementContract);
let client = IncrementContractClient::new(&env, &contract_id);

fn make_identifier(e: &Env, kp: &Keypair) -> Identifier {
Identifier::Ed25519(kp.public.to_bytes().into_val(e))
}
let user_1 = env.accounts().generate();
let user_2 = env.accounts().generate();

fn make_signature(e: &Env, kp: &Keypair, function: &str, args: Vec<RawVal>) -> Signature {
let msg = SignaturePayload::V0(SignaturePayloadV0 {
name: Symbol::from_str(function),
contract: BytesN::from_array(e, &[0; 32]),
network: e.ledger().network_passphrase(),
args,
});
Signature::Ed25519(Ed25519Signature {
public_key: BytesN::from_array(e, &kp.public.to_bytes()),
signature: kp.sign(msg).unwrap().into_val(e),
})
assert_eq!(
client
.with_source_account(&user_1)
.increment(&Signature::Invoker, &BigInt::zero(&env)),
1
);
assert_eq!(
client
.with_source_account(&user_1)
.increment(&Signature::Invoker, &BigInt::zero(&env)),
2
);
assert_eq!(
client
.with_source_account(&user_2)
.increment(&Signature::Invoker, &BigInt::zero(&env)),
1
);
assert_eq!(
client
.with_source_account(&user_1)
.increment(&Signature::Invoker, &BigInt::zero(&env)),
3
);
}

#[test]
fn test() {
fn test_auth_with_ed25519() {
let env = Env::default();
let contract_id = BytesN::from_array(&env, &[0; 32]);
env.register_contract(&contract_id, ExampleContract);
let client = ExampleContractClient::new(&env, contract_id);
env.register_contract(&contract_id, IncrementContract);
let client = IncrementContractClient::new(&env, contract_id.clone());

// 1. Initialize contract by setting the admin.
let admin_kp = generate_keypair();
let admin_id = make_identifier(&env, &admin_kp);
client.set_admin(&admin_id);
let (user_1_id, user_1_sign) = soroban_auth::testutils::ed25519::generate(&env);
let (user_2_id, user_2_sign) = soroban_auth::testutils::ed25519::generate(&env);

// 2. Store a num for user1.
let user1_kp = generate_keypair();
let user1_id = make_identifier(&env, &user1_kp);
let num = BigInt::from_u32(&env, 2);

let user1_nonce = client.nonce(&user1_id);

let sig = make_signature(
let nonce = BigInt::from_u32(&env, 0);
let sig = soroban_auth::testutils::ed25519::sign(
&env,
&user1_kp,
"save_num",
(&user1_id, &user1_nonce, &num).into_val(&env),
&user_1_sign,
&contract_id,
symbol!("increment"),
(&user_1_id, &nonce),
);
client.save_num(&sig, &user1_nonce, &num);
assert_eq!(client.increment(&sig, &nonce), 1);

// 3. Overwrite user1's num using admin.
let new_num = BigInt::from_u32(&env, 10);
let nonce = BigInt::from_u32(&env, 1);
let sig = soroban_auth::testutils::ed25519::sign(
&env,
&user_1_sign,
&contract_id,
symbol!("increment"),
(&user_1_id, &nonce),
);
assert_eq!(client.increment(&sig, &nonce), 2);

let admin_nonce = client.nonce(&admin_id);
let sig = make_signature(
let nonce = BigInt::from_u32(&env, 0);
let sig = soroban_auth::testutils::ed25519::sign(
&env,
&admin_kp,
"overwrite",
(&admin_id, &admin_nonce, &user1_id, &new_num).into_val(&env),
&user_2_sign,
&contract_id,
symbol!("increment"),
(&user_2_id, &nonce),
);
assert_eq!(client.increment(&sig, &nonce), 1);

client.overwrite(&sig, &admin_nonce, &user1_id, &new_num);
let nonce = BigInt::from_u32(&env, 2);
let sig = soroban_auth::testutils::ed25519::sign(
&env,
&user_1_sign,
&contract_id,
symbol!("increment"),
(&user_1_id, &nonce),
);
assert_eq!(client.increment(&sig, &nonce), 3);
}

#[test]
#[should_panic(expected = "Failed ED25519 verification")]
fn bad_data() {
#[should_panic(expected = "Status(UnknownError(0))")]
fn test_auth_with_ed25519_wrong_signer() {
let env = Env::default();
let contract_id = BytesN::from_array(&env, &[0; 32]);
env.register_contract(&contract_id, ExampleContract);
let client = ExampleContractClient::new(&env, contract_id);
env.register_contract(&contract_id, IncrementContract);
let client = IncrementContractClient::new(&env, contract_id.clone());

// 1. Sign arguments with user1's keypair.
let user1_kp = generate_keypair();
let user1_id = make_identifier(&env, &user1_kp);
let signed_num = BigInt::from_u32(&env, 1);
let (user_1_id, _) = soroban_auth::testutils::ed25519::generate(&env);
let (_, user_2_sign) = soroban_auth::testutils::ed25519::generate(&env);

let nonce = client.nonce(&user1_id);

let sig = make_signature(
// User 2 signs but claims to be user 1.
let nonce = BigInt::from_u32(&env, 0);
let sig = soroban_auth::testutils::ed25519::sign(
&env,
&user1_kp,
"save_data",
(&user1_id, &nonce, &signed_num).into_val(&env),
&user_2_sign,
&contract_id,
symbol!("increment"),
(&user_1_id, &nonce),
);
assert_eq!(client.increment(&sig, &nonce), 1);
}

#[test]
#[should_panic(expected = "Status(ContractError(2))")]
fn test_auth_with_ed25519_wrong_nonce() {
let env = Env::default();
let contract_id = BytesN::from_array(&env, &[0; 32]);
env.register_contract(&contract_id, IncrementContract);
let client = IncrementContractClient::new(&env, contract_id.clone());

// 2. Attempt to invoke with user1's signature, but with different
// arguments. Expect panic.
let bad_num = BigInt::from_u32(&env, 2);
client.save_num(&sig, &nonce, &bad_num);
let (user_1_id, user_1_sign) = soroban_auth::testutils::ed25519::generate(&env);

// User 1 signs using incorrect next expected nonce.
let nonce = BigInt::from_u32(&env, 1);
let sig = soroban_auth::testutils::ed25519::sign(
&env,
&user_1_sign,
&contract_id,
symbol!("increment"),
(&user_1_id, &nonce),
);
assert_eq!(client.increment(&sig, &nonce), 1);
}

0 comments on commit 73e258a

Please sign in to comment.