forked from stellar/soroban-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change advanced-auth example to be built on increment example (stella…
- Loading branch information
1 parent
71c07f0
commit 73e258a
Showing
2 changed files
with
172 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |