Skip to content

Commit

Permalink
add Source (nonce/salt)
Browse files Browse the repository at this point in the history
  • Loading branch information
notV4l committed Oct 11, 2024
1 parent fa7e8d9 commit ef6ca3e
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 81 deletions.
25 changes: 20 additions & 5 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
# VRF for Cartridge Controller with paymaster

Randomness is requested by calling `vrf_provider.request_random` with:
- caller : the contract that will call consume_random
- source : one of this 2 options :

**Source::Nonce(address)** :
- each request_random will generate a unique seed using a nonce by address (nonce is increased after each call)
- (its recommanded to use wallet address to avoid nonce collisions)

**Source::Salt(salt)** :
- you have to provider you own salt
- using same salt will generate same seed = same randomness

# VRF for Cartridge Controller with paymaster

## How it works

caller send multicall :

```
[
vrf_provider.request_random(),
vrf_provider.request_random(game_contract.address, Source::Nonce(wallet_address)),
game_contract.you_function_consuming_randomness(...params)
]
```

Cartridge backend receive the tx,
retrieve seed using vrf_provider.get_next_seed( caller ),
compute proof for seed
and inject calls to sandwitch caller in a multicall :

```
[
vrf_provider.submit_random( seed, proof),
controller.outside_execution([
vrf_provider.request_random(),
vrf_provider.request_random(game_contract.address, Source::Nonce(wallet_address)),
game_contract.you_function_consuming_randomness(...params)
])
vrf_provider.assert_consumed( seed ),
Expand All @@ -31,6 +46,6 @@ and inject calls to sandwitch caller in a multicall :
- Randomness must be consume
- Randomness can only be consumed once
- Tx (submit_random / user calls / assert_consumed) is executed atomically by Cartridge backend
- Sumbitted randomness only last for the tx duration
- Sumbitted randomness only last for the tx duration
- It's not possible to request_random in a tx and consume_random in another tx
- User cannot probe randomness
- User cannot probe randomness
3 changes: 2 additions & 1 deletion contracts/dojo/vrf_consumer_template.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod consumer_template {
use starknet::get_caller_address;

use vrf_contracts::vrf_consumer::vrf_consumer_component::VrfConsumerComponent;
use vrf_contracts::vrf_provider::vrf_provider_component::Source;

component!(path: VrfConsumerComponent, storage: vrf_consumer, event: VrfConsumerEvent);

Expand Down Expand Up @@ -39,7 +40,7 @@ mod consumer_template {
impl VrfConsumerTemplateImpl of super::IVrfConsumerTemplate<ContractState> {
fn dice(ref self: ContractState) {
let player_id = get_caller_address();
let random: u256 = self.vrf_consumer.consume_random(player_id).into();
let random: u256 = self.vrf_consumer.consume_random(Source::Nonce(player_id)).into();
let value: u8 = (random % 6).try_into().unwrap() + 1;
// do the right things
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/src/tests/common.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub fn CONSUMER2() -> ContractAddress {
contract_address_const::<'CONSUMER2'>()
}

pub fn PLAYER1() -> ContractAddress {
contract_address_const::<'PLAYER1'>()
}

#[derive(Drop, Copy, Clone)]
pub struct SetupResult {
provider: IVrfProviderDispatcher,
Expand Down
138 changes: 87 additions & 51 deletions contracts/src/tests/test_dice.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,55 +10,64 @@ use openzeppelin_utils::serde::SerializedAppend;

use vrf_contracts::vrf_provider::vrf_provider::VrfProvider;
use vrf_contracts::vrf_provider::vrf_provider_component::{
IVrfProvider, IVrfProviderDispatcher, IVrfProviderDispatcherTrait, PublicKey,
IVrfProvider, IVrfProviderDispatcher, IVrfProviderDispatcherTrait, PublicKey, Source
};

use vrf_contracts::vrf_consumer::vrf_consumer_example::{
VrfConsumer, IVrfConsumerExample, IVrfConsumerExampleDispatcher,
IVrfConsumerExampleDispatcherTrait
};

use super::common::{setup, submit_random, SetupResult, CONSUMER1, CONSUMER2};
use super::common::{setup, submit_random, SetupResult, CONSUMER1, CONSUMER2, PLAYER1};

// private key: 420
// {"public_key_x":"0x66da5d53168d591c55d4c05f3681663ac51bcdccd5ca09e366b71b0c40ccff4","public_key_y":"0x6d3eb29920bf55195e5ec76f69e247c0942c7ef85f6640896c058ec75ca2232"}
// seed: 0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09
// curl -X POST -H "Content-Type: application/json" -d '{"seed": ["0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09"]}' http://0.0.0.0:3000/stark_vrf
// {
// "result": {
// "gamma_x": "0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d",
// "gamma_y": "0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8",
// "c": "0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302",
// "s": "0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6",
// "sqrt_ratio": "0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01",
// "rnd": "0x735e9c275caf267880ec4e5967fde13ca084244384c03c739fcc54ac23789a4"
// }
// }


const SEED: felt252 = 0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09;

// curl -X POST -H "Content-Type: application/json" -d '{"seed": ["0x334b8c0ea68406b183b5affd81ce11bec1a0807d3fd68a54ee75ec148053b09"]}' http://0.0.0.0:3000/stark_vrf
pub fn proof() -> Proof {
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
}
}

const SEED_FROM_SALT: felt252 = 0x767EBFD1241683397A6CB06FDE012811BB27FD6E768D7A4BB8670ED10DF95C0;

// curl -X POST -H "Content-Type: application/json" -d '{"seed": ["0x767EBFD1241683397A6CB06FDE012811BB27FD6E768D7A4BB8670ED10DF95C0"]}' http://0.0.0.0:3000/stark_vrf
pub fn proof_from_salt() -> Proof {
Proof {
gamma: Point {
x: 0x28473f4cad1406e83a766a1137281340b93600661af4eda228d5f73ae4e0fe9,
y: 0x36bf0d2884aa739d5e419f1eb9fdf4af61c489169830d748ae0bbc7707b95ae
},
c: 0x137ed85cd1ae3b25d6f9c2e3e23912cde001696fb4c0caba403967ecdab4bb4,
s: 0x74b0e76d8cfffc8a4755f4bcdefd3b1339d68e2c06b1072b36cfb2661fd4a27,
sqrt_ratio_hint: 0x29523a3636251c7085188108d8912577076ab6337892a3aa492dea4015d3a0c,
}
}

#[test]
fn test_dice() {
let setup = setup();

setup.provider.request_random(CALLER(), Option::None);
setup.provider.request_random(CONSUMER1(), Source::Nonce(PLAYER1()));

submit_random(
setup.provider,
SEED,
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
},
proof(),
);

// CALLER consume
start_cheat_caller_address(setup.consumer1.contract_address, CALLER());
// PLAYER1 call dice, CONSUMER1 is caller of consume_random
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
let dice1 = setup.consumer1.dice();
assert(dice1 == 3, 'dice1 should be 3');
stop_cheat_caller_address(setup.consumer1.contract_address);
Expand All @@ -71,24 +80,17 @@ fn test_dice() {
fn test_not_consuming__must_consume() {
let setup = setup();

setup.provider.request_random(CALLER(), Option::None);
// noop just here for example
setup.provider.request_random(CONSUMER1(), Source::Nonce(PLAYER1()));

submit_random(
setup.provider,
SEED,
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
},
proof(),
);

// CALLER dont consume
start_cheat_caller_address(setup.consumer1.contract_address, CALLER());
// PLAYER1 dont consume
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
setup.consumer1.not_consuming();

stop_cheat_caller_address(setup.consumer1.contract_address);
Expand All @@ -100,27 +102,61 @@ fn test_not_consuming__must_consume() {
fn test_dice__cannot_consume_twice() {
let setup = setup();

setup.provider.request_random(CALLER(), Option::None);
// noop just here for example
setup.provider.request_random(CONSUMER1(), Source::Nonce(PLAYER1()));

// provider submit_random
submit_random(
setup.provider,
SEED,
Proof {
gamma: Point {
x: 0xf010d3727eb8aee76c7bc81f399805f4c2c39708451d933ef4d7f909248a6d,
y: 0x18a8fab3c58608505953d0fa0376ab454907d6e88db83702a36294faa937ac8
},
c: 0x10e06538fdb8d943ecbf03e519500e258a83248d5a457ff2803c54c583f6302,
s: 0x150f672c657e116cd3966b74a2320e600c853801612b56f3a9cb31063f763c6,
sqrt_ratio_hint: 0x8b09cf018201f7702d638b23d3cd10f577f7973369e79e5974ab33c1d64e01,
},
proof(),
);

// CALLER consume
start_cheat_caller_address(setup.consumer1.contract_address, CALLER());
start_cheat_caller_address(setup.consumer2.contract_address, CALLER());
// PLAYER1 consume twice
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
start_cheat_caller_address(setup.consumer2.contract_address, PLAYER1());

let _dice1 = setup.consumer1.dice();
let _dice2 = setup.consumer1.dice();
}

#[test]
fn test_dice_with_salt() {
let setup = setup();

// noop just here for example
setup.provider.request_random(CONSUMER1(),Source::Salt('salt'));

submit_random(
setup.provider,
SEED_FROM_SALT,
proof_from_salt(),
);

// PLAYER1 call dice_with_salt, CONSUMER1 is caller of consume_random
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
let dice1 = setup.consumer1.dice_with_salt();
assert(dice1 == 2, 'dice1 should be 2');
stop_cheat_caller_address(setup.consumer1.contract_address);

setup.provider.assert_consumed(SEED);
}

#[test]
#[should_panic(expected: 'VrfProvider: not fulfilled')]
fn test_dice_with_salt__wrong_proof() {
let setup = setup();

// noop just here for example
setup.provider.request_random(CONSUMER1(),Source::Salt('salt'));

submit_random(
setup.provider,
SEED,
proof(),
);

// PLAYER1 consume
start_cheat_caller_address(setup.consumer1.contract_address, PLAYER1());
let _dice1 = setup.consumer1.dice_with_salt();
}
8 changes: 3 additions & 5 deletions contracts/src/vrf_consumer/vrf_consumer_component.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod VrfConsumerComponent {

use vrf_contracts::vrf_provider::vrf_provider_component::{
IVrfProvider, IVrfProviderDispatcher, IVrfProviderDispatcherTrait, PublicKey,
PublicKeyIntoPoint
PublicKeyIntoPoint, Source
};

#[storage]
Expand Down Expand Up @@ -67,10 +67,8 @@ pub mod VrfConsumerComponent {
self.set_vrf_provider(vrf_provider);
}

fn consume_random(
self: @ComponentState<TContractState>, caller: ContractAddress
) -> felt252 {
self.vrf_provider_disp().consume_random(Option::None)
fn consume_random(self: @ComponentState<TContractState>, source: Source) -> felt252 {
self.vrf_provider_disp().consume_random(source)
}

fn vrf_provider_disp(self: @ComponentState<TContractState>,) -> IVrfProviderDispatcher {
Expand Down
15 changes: 12 additions & 3 deletions contracts/src/vrf_consumer/vrf_consumer_example.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#[starknet::interface]
trait IVrfConsumerExample<TContractState> {
fn dice(ref self: TContractState) -> u8;
fn dice_with_salt(ref self: TContractState) -> u8;

fn not_consuming(ref self: TContractState);

// admin
Expand All @@ -22,6 +24,7 @@ mod VrfConsumer {
use stark_vrf::ecvrf::{Point, Proof, ECVRF, ECVRFImpl};

use vrf_contracts::vrf_consumer::vrf_consumer_component::{VrfConsumerComponent};
use vrf_contracts::vrf_provider::vrf_provider_component::Source;

component!(path: VrfConsumerComponent, storage: vrf_consumer, event: VrfConsumerEvent);

Expand Down Expand Up @@ -52,15 +55,21 @@ mod VrfConsumer {
impl ConsumerImpl of super::IVrfConsumerExample<ContractState> {
// throw dice
fn dice(ref self: ContractState) -> u8 {
let caller = get_caller_address();
let random: u256 = self.vrf_consumer.consume_random(caller).into();
let player_id = get_caller_address();
let random: u256 = self.vrf_consumer.consume_random(Source::Nonce(player_id)).into();

((random % 6) + 1).try_into().unwrap()
}

fn dice_with_salt(ref self: ContractState) -> u8 {
let random: u256 = self.vrf_consumer.consume_random(Source::Salt('salt')).into();

((random % 6) + 1).try_into().unwrap()
}

fn not_consuming(ref self: ContractState) {
let _player_id = get_caller_address();
// do the nothing
// do the nothing
}

fn set_vrf_provider(ref self: ContractState, new_vrf_provider: ContractAddress) {
Expand Down
Loading

0 comments on commit ef6ca3e

Please sign in to comment.