Add protocol state machine

This commit is contained in:
rishflab 2021-05-05 13:31:07 +10:00
parent ccb808bdf7
commit acb243a0a2
8 changed files with 590 additions and 281 deletions

153
monero-adaptor/src/alice.rs Normal file
View File

@ -0,0 +1,153 @@
use crate::{
final_challenge, AdaptorSignature, Commitment, DleqProof, Message0, Message1, Message2,
Message3, Opening, RING_SIZE,
};
use anyhow::Result;
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
use curve25519_dalek::edwards::EdwardsPoint;
use curve25519_dalek::scalar::Scalar;
use hash_edwards_to_edwards::hash_point_to_point;
use rand::rngs::OsRng;
pub enum AliceState {
WaitingForCommitment(Alice0),
WaitingForScalar(Alice1),
PublishXmr,
RedeemBtc,
PunishXmr,
}
pub struct Alice0 {
// secret index is always 0
ring: [EdwardsPoint; RING_SIZE],
fake_responses: [Scalar; RING_SIZE - 1],
msg: [u8; 32],
// encryption key
R_a: EdwardsPoint,
// R'a = r_a*H_p(p_k) where p_k is the signing public key
R_prime_a: EdwardsPoint,
// this is not s_a cos of something to with one-time-address??
s_prime_a: Scalar,
// secret value:
alpha_a: Scalar,
H_p_pk: EdwardsPoint,
I_a: EdwardsPoint,
I_hat_a: EdwardsPoint,
T_a: EdwardsPoint,
}
impl Alice0 {
pub fn new(
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
R_a: EdwardsPoint,
R_prime_a: EdwardsPoint,
s_prime_a: Scalar,
) -> Result<Self> {
let mut fake_responses = [Scalar::zero(); RING_SIZE - 1];
for response in fake_responses.iter_mut().take(RING_SIZE - 1) {
*response = Scalar::random(&mut OsRng);
}
let alpha_a = Scalar::random(&mut OsRng);
let p_k = ring[0];
let H_p_pk = hash_point_to_point(p_k);
let I_a = s_prime_a * H_p_pk;
let I_hat_a = alpha_a * H_p_pk;
let T_a = alpha_a * ED25519_BASEPOINT_POINT;
Ok(Alice0 {
ring,
fake_responses,
msg,
R_a,
R_prime_a,
s_prime_a,
alpha_a,
H_p_pk,
I_a,
I_hat_a,
T_a,
})
}
pub fn next_message(&self) -> Message0 {
Message0 {
pi_a: DleqProof::new(
ED25519_BASEPOINT_POINT,
self.T_a,
self.H_p_pk,
self.I_hat_a,
self.alpha_a,
),
c_a: Commitment::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a),
}
}
pub fn receive(self, msg: Message1) -> Result<Alice1> {
msg.pi_b
.verify(ED25519_BASEPOINT_POINT, msg.T_b, self.H_p_pk, msg.I_hat_b)?;
let (h_last, h_0) = final_challenge(
self.fake_responses,
self.ring,
self.T_a,
msg.T_b,
self.R_a,
self.I_hat_a,
msg.I_hat_b,
self.R_prime_a,
self.I_a,
msg.I_b,
self.msg,
)?;
let s_0_a = self.alpha_a - h_last * self.s_prime_a;
Ok(Alice1 {
fake_responses: self.fake_responses,
h_0,
I_b: msg.I_b,
s_0_a,
I_a: self.I_a,
I_hat_a: self.I_hat_a,
T_a: self.T_a,
})
}
}
pub struct Alice1 {
fake_responses: [Scalar; RING_SIZE - 1],
I_a: EdwardsPoint,
I_hat_a: EdwardsPoint,
T_a: EdwardsPoint,
h_0: Scalar,
I_b: EdwardsPoint,
s_0_a: Scalar,
}
impl Alice1 {
pub fn next_message(&self) -> Message2 {
Message2 {
d_a: Opening::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a),
s_0_a: self.s_0_a,
}
}
pub fn receive(self, msg: Message3) -> Alice2 {
let adaptor_sig = AdaptorSignature {
s_0_a: self.s_0_a,
s_0_b: msg.s_0_b,
fake_responses: self.fake_responses,
h_0: self.h_0,
I: self.I_a + self.I_b,
};
Alice2 { adaptor_sig }
}
}
pub struct Alice2 {
pub adaptor_sig: AdaptorSignature,
}

164
monero-adaptor/src/bob.rs Normal file
View File

@ -0,0 +1,164 @@
use crate::{
final_challenge, AdaptorSignature, Commitment, DleqProof, Message0, Message1, Message2,
Message3, RING_SIZE,
};
use anyhow::Result;
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
use curve25519_dalek::edwards::EdwardsPoint;
use curve25519_dalek::scalar::Scalar;
use hash_edwards_to_edwards::hash_point_to_point;
use rand::rngs::OsRng;
pub enum BobState {
WaitingForCommitment(Bob0),
WaitingForOpening(Bob1),
PublishXmr,
RedeemBtc,
PunishXmr,
}
pub struct Bob0 {
// secret index is always 0
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
// encryption key
R_a: EdwardsPoint,
// R'a = r_a*H_p(p_k) where p_k is the signing public key
R_prime_a: EdwardsPoint,
s_b: Scalar,
// secret value:
alpha_b: Scalar,
H_p_pk: EdwardsPoint,
I_b: EdwardsPoint,
I_hat_b: EdwardsPoint,
T_b: EdwardsPoint,
}
impl Bob0 {
pub fn new(
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
R_a: EdwardsPoint,
R_prime_a: EdwardsPoint,
s_b: Scalar,
) -> Result<Self> {
let alpha_b = Scalar::random(&mut OsRng);
let p_k = ring[0];
let H_p_pk = hash_point_to_point(p_k);
let I_b = s_b * H_p_pk;
let I_hat_b = alpha_b * H_p_pk;
let T_b = alpha_b * ED25519_BASEPOINT_POINT;
Ok(Bob0 {
ring,
msg,
R_a,
R_prime_a,
s_b,
alpha_b,
H_p_pk,
I_b,
I_hat_b,
T_b,
})
}
pub fn receive(self, msg: Message0) -> Bob1 {
Bob1 {
ring: self.ring,
msg: self.msg,
R_a: self.R_a,
R_prime_a: self.R_prime_a,
s_b: self.s_b,
alpha_b: self.alpha_b,
H_p_pk: self.H_p_pk,
I_b: self.I_b,
I_hat_b: self.I_hat_b,
T_b: self.T_b,
pi_a: msg.pi_a,
c_a: msg.c_a,
}
}
}
pub struct Bob1 {
// secret index is always 0
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
// encryption key
R_a: EdwardsPoint,
// R'a = r_a*H_p(p_k) where p_k is the signing public key
R_prime_a: EdwardsPoint,
s_b: Scalar,
// secret value:
alpha_b: Scalar,
H_p_pk: EdwardsPoint,
I_b: EdwardsPoint,
I_hat_b: EdwardsPoint,
T_b: EdwardsPoint,
pi_a: DleqProof,
c_a: Commitment,
}
impl Bob1 {
pub fn next_message(&self) -> Message1 {
Message1 {
I_b: self.I_b,
T_b: self.T_b,
I_hat_b: self.I_hat_b,
pi_b: DleqProof::new(
ED25519_BASEPOINT_POINT,
self.T_b,
self.H_p_pk,
self.I_hat_b,
self.alpha_b,
),
}
}
pub fn receive(self, msg: Message2) -> Result<Bob2> {
let (fake_responses, I_a, I_hat_a, T_a) = msg.d_a.open(self.c_a)?;
self.pi_a
.verify(ED25519_BASEPOINT_POINT, T_a, self.H_p_pk, I_hat_a)?;
let (h_last, h_0) = final_challenge(
fake_responses,
self.ring,
T_a,
self.T_b,
self.R_a,
I_hat_a,
self.I_hat_b,
self.R_prime_a,
I_a,
self.I_b,
self.msg,
)?;
let s_0_b = self.alpha_b - h_last * self.s_b;
let adaptor_sig = AdaptorSignature {
s_0_a: msg.s_0_a,
s_0_b,
fake_responses,
h_0,
I: I_a + self.I_b,
};
Ok(Bob2 { s_0_b, adaptor_sig })
}
}
pub struct Bob2 {
s_0_b: Scalar,
pub adaptor_sig: AdaptorSignature,
}
impl Bob2 {
pub fn next_message(&self) -> Message3 {
Message3 { s_0_b: self.s_0_b }
}
}

View File

@ -11,6 +11,9 @@ use rand::rngs::OsRng;
use std::convert::TryInto;
use tiny_keccak::{Hasher, Keccak};
mod alice;
mod bob;
const RING_SIZE: usize = 11;
const DOMAIN_TAG: &str = "CSLAG_c";
@ -154,287 +157,6 @@ impl Signature {
}
}
pub struct Alice0 {
// secret index is always 0
ring: [EdwardsPoint; RING_SIZE],
fake_responses: [Scalar; RING_SIZE - 1],
msg: [u8; 32],
// encryption key
R_a: EdwardsPoint,
// R'a = r_a*H_p(p_k) where p_k is the signing public key
R_prime_a: EdwardsPoint,
// this is not s_a cos of something to with one-time-address??
s_prime_a: Scalar,
// secret value:
alpha_a: Scalar,
H_p_pk: EdwardsPoint,
I_a: EdwardsPoint,
I_hat_a: EdwardsPoint,
T_a: EdwardsPoint,
}
impl Alice0 {
pub fn new(
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
R_a: EdwardsPoint,
R_prime_a: EdwardsPoint,
s_prime_a: Scalar,
) -> Result<Self> {
let mut fake_responses = [Scalar::zero(); RING_SIZE - 1];
for response in fake_responses.iter_mut().take(RING_SIZE - 1) {
*response = Scalar::random(&mut OsRng);
}
let alpha_a = Scalar::random(&mut OsRng);
let p_k = ring[0];
let H_p_pk = hash_point_to_point(p_k);
let I_a = s_prime_a * H_p_pk;
let I_hat_a = alpha_a * H_p_pk;
let T_a = alpha_a * ED25519_BASEPOINT_POINT;
Ok(Alice0 {
ring,
fake_responses,
msg,
R_a,
R_prime_a,
s_prime_a,
alpha_a,
H_p_pk,
I_a,
I_hat_a,
T_a,
})
}
pub fn next_message(&self) -> Message0 {
Message0 {
pi_a: DleqProof::new(
ED25519_BASEPOINT_POINT,
self.T_a,
self.H_p_pk,
self.I_hat_a,
self.alpha_a,
),
c_a: Commitment::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a),
}
}
pub fn receive(self, msg: Message1) -> Result<Alice1> {
msg.pi_b
.verify(ED25519_BASEPOINT_POINT, msg.T_b, self.H_p_pk, msg.I_hat_b)?;
let (h_last, h_0) = final_challenge(
self.fake_responses,
self.ring,
self.T_a,
msg.T_b,
self.R_a,
self.I_hat_a,
msg.I_hat_b,
self.R_prime_a,
self.I_a,
msg.I_b,
self.msg,
)?;
let s_0_a = self.alpha_a - h_last * self.s_prime_a;
Ok(Alice1 {
fake_responses: self.fake_responses,
h_0,
I_b: msg.I_b,
s_0_a,
I_a: self.I_a,
I_hat_a: self.I_hat_a,
T_a: self.T_a,
})
}
}
pub struct Alice1 {
fake_responses: [Scalar; RING_SIZE - 1],
I_a: EdwardsPoint,
I_hat_a: EdwardsPoint,
T_a: EdwardsPoint,
h_0: Scalar,
I_b: EdwardsPoint,
s_0_a: Scalar,
}
impl Alice1 {
pub fn next_message(&self) -> Message2 {
Message2 {
d_a: Opening::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a),
s_0_a: self.s_0_a,
}
}
pub fn receive(self, msg: Message3) -> Alice2 {
let adaptor_sig = AdaptorSignature {
s_0_a: self.s_0_a,
s_0_b: msg.s_0_b,
fake_responses: self.fake_responses,
h_0: self.h_0,
I: self.I_a + self.I_b,
};
Alice2 { adaptor_sig }
}
}
pub struct Alice2 {
pub adaptor_sig: AdaptorSignature,
}
pub struct Bob0 {
// secret index is always 0
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
// encryption key
R_a: EdwardsPoint,
// R'a = r_a*H_p(p_k) where p_k is the signing public key
R_prime_a: EdwardsPoint,
s_b: Scalar,
// secret value:
alpha_b: Scalar,
H_p_pk: EdwardsPoint,
I_b: EdwardsPoint,
I_hat_b: EdwardsPoint,
T_b: EdwardsPoint,
}
impl Bob0 {
pub fn new(
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
R_a: EdwardsPoint,
R_prime_a: EdwardsPoint,
s_b: Scalar,
) -> Result<Self> {
let alpha_b = Scalar::random(&mut OsRng);
let p_k = ring[0];
let H_p_pk = hash_point_to_point(p_k);
let I_b = s_b * H_p_pk;
let I_hat_b = alpha_b * H_p_pk;
let T_b = alpha_b * ED25519_BASEPOINT_POINT;
Ok(Bob0 {
ring,
msg,
R_a,
R_prime_a,
s_b,
alpha_b,
H_p_pk,
I_b,
I_hat_b,
T_b,
})
}
pub fn receive(self, msg: Message0) -> Bob1 {
Bob1 {
ring: self.ring,
msg: self.msg,
R_a: self.R_a,
R_prime_a: self.R_prime_a,
s_b: self.s_b,
alpha_b: self.alpha_b,
H_p_pk: self.H_p_pk,
I_b: self.I_b,
I_hat_b: self.I_hat_b,
T_b: self.T_b,
pi_a: msg.pi_a,
c_a: msg.c_a,
}
}
}
pub struct Bob1 {
// secret index is always 0
ring: [EdwardsPoint; RING_SIZE],
msg: [u8; 32],
// encryption key
R_a: EdwardsPoint,
// R'a = r_a*H_p(p_k) where p_k is the signing public key
R_prime_a: EdwardsPoint,
s_b: Scalar,
// secret value:
alpha_b: Scalar,
H_p_pk: EdwardsPoint,
I_b: EdwardsPoint,
I_hat_b: EdwardsPoint,
T_b: EdwardsPoint,
pi_a: DleqProof,
c_a: Commitment,
}
impl Bob1 {
pub fn next_message(&self) -> Message1 {
Message1 {
I_b: self.I_b,
T_b: self.T_b,
I_hat_b: self.I_hat_b,
pi_b: DleqProof::new(
ED25519_BASEPOINT_POINT,
self.T_b,
self.H_p_pk,
self.I_hat_b,
self.alpha_b,
),
}
}
pub fn receive(self, msg: Message2) -> Result<Bob2> {
let (fake_responses, I_a, I_hat_a, T_a) = msg.d_a.open(self.c_a)?;
self.pi_a
.verify(ED25519_BASEPOINT_POINT, T_a, self.H_p_pk, I_hat_a)?;
let (h_last, h_0) = final_challenge(
fake_responses,
self.ring,
T_a,
self.T_b,
self.R_a,
I_hat_a,
self.I_hat_b,
self.R_prime_a,
I_a,
self.I_b,
self.msg,
)?;
let s_0_b = self.alpha_b - h_last * self.s_b;
let adaptor_sig = AdaptorSignature {
s_0_a: msg.s_0_a,
s_0_b,
fake_responses,
h_0,
I: I_a + self.I_b,
};
Ok(Bob2 { s_0_b, adaptor_sig })
}
}
pub struct Bob2 {
s_0_b: Scalar,
pub adaptor_sig: AdaptorSignature,
}
impl Bob2 {
pub fn next_message(&self) -> Message3 {
Message3 { s_0_b: self.s_0_b }
}
}
struct DleqProof {
s: Scalar,
c: Scalar,
@ -603,6 +325,8 @@ pub struct Message3 {
#[cfg(test)]
mod tests {
use super::*;
use crate::alice::Alice0;
use crate::bob::Bob0;
#[test]
fn sign_and_verify_success() {

View File

@ -21,6 +21,7 @@ pub fn init(level: LevelFilter) -> Result<()> {
builder.init();
}
tracing
tracing::info!("Initialized tracing with level: {}", level);
Ok(())

View File

@ -27,5 +27,6 @@ pub mod monero;
pub mod network;
pub mod protocol;
pub mod seed;
mod xmr_first_protocol;
mod monero_ext;

View File

@ -0,0 +1,6 @@
mod alice;
mod bob;
pub trait Persist {
fn persist(&self);
}

View File

@ -0,0 +1,133 @@
use crate::xmr_first_protocol::Persist;
use std::collections::VecDeque;
use std::task::Poll;
pub struct StateMachine {
state: State,
actions: VecDeque<Action>,
events: VecDeque<Event>,
}
impl StateMachine {
fn inject_event(&mut self, event: Event) {
match self.state {
State::WatchingForBtcLock => match event {
Event::BtcLockSeenInMempool => {
self.actions.push_back(Action::SignAndBroadcastBtcRedeem);
self.actions.push_back(Action::WatchForXmrRedeem);
self.state = State::WatchingForXmrRedeem;
}
Event::BtcLockTimeoutElapsed => {
self.actions.push_back(Action::BroadcastXmrRefund);
self.state = State::Aborted;
}
_ => {}
},
State::WatchingForXmrRedeem => match event {
Event::T2Elapsed => {
self.actions.push_back(Action::BroadcastXmrRefund);
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
self.state = State::Punished;
}
Event::XmrRedeemSeenInMempool => {
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
self.state = State::Success;
}
_ => {}
},
// State::WaitingForT2 => match event {
// Event::T2Elapsed => {
// self.actions.push_back(Action::SignAndBroadcastBtcPunish);
// self.actions.push_back(Action::BroadcastXmrRefund);
// self.state = State::Success;
// }
// _ => {}
// },
_ => {}
}
}
fn poll(&mut self) -> Poll<Action> {
if let Some(action) = self.actions.pop_front() {
Poll::Ready(action)
} else {
Poll::Pending
}
}
}
impl Persist for Event {
fn persist(&self) {
todo!()
}
}
#[derive(PartialEq, Debug)]
pub enum State {
WatchingForBtcLock,
WatchingForXmrRedeem,
// WaitingForT2,
Punished,
Success,
Aborted,
}
pub enum Event {
BtcLockSeenInMempool,
T2Elapsed,
BtcLockTimeoutElapsed,
XmrRedeemSeenInMempool,
}
// These actions should not fail (are retried until successful) and should be
// idempotent This allows us to greatly simplify the state machine
pub enum Action {
WatchForXmrRedeem,
SignAndBroadcastBtcPunish,
SignAndBroadcastBtcRedeem,
BroadcastXmrRefund,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn happy_path() {
let mut state_machine = StateMachine {
state: State::WatchingForBtcLock,
actions: Default::default(),
events: Default::default(),
};
state_machine.inject_event(Event::BtcLockSeenInMempool);
assert_eq!(state_machine.poll())
state_machine.inject_event(Event::XmrRedeemSeenInMempool);
assert_eq!(state_machine.state, State::Success);
}
#[test]
fn bob_fails_to_lock_btc() {
let mut state_machine = StateMachine {
state: State::WatchingForBtcLock,
actions: Default::default(),
events: Default::default(),
};
state_machine.events.push_back(Event::BtcLockTimeoutElapsed);
state_machine.run();
assert_eq!(state_machine.state, State::Aborted);
}
#[test]
fn bob_fails_to_redeem_xmr_before_t2() {
let mut state_machine = StateMachine {
state: State::WatchingForBtcLock,
actions: Default::default(),
events: Default::default(),
};
state_machine.events.push_back(Event::BtcLockSeenInMempool);
state_machine.events.push_back(Event::T2Elapsed);
state_machine.run();
assert_eq!(state_machine.state, State::Punished);
}
}

View File

@ -0,0 +1,127 @@
use crate::monero::TransferProof;
use crate::xmr_first_protocol::Persist;
use std::collections::VecDeque;
pub struct StateMachine {
state: State,
actions: VecDeque<Action>,
events: VecDeque<Event>,
}
impl StateMachine {
fn next(&mut self, event: Event) {
match self.state {
State::WatchingForXmrLock => match event {
Event::XmrConfirmed => {
self.actions.push_back(Action::SignAndBroadcastBtcLock);
self.state = State::WaitingForBtcRedeem;
}
Event::T1Elapsed => {
self.state = State::Aborted;
}
Event::XmrRefundSeenInMempool => {
self.state = State::Aborted;
}
_ => panic!("unhandled scenario"),
},
State::WaitingForBtcRedeem => match event {
Event::BtcRedeemSeenInMempool => {
self.actions.push_back(Action::BroadcastXmrRedeem);
self.state = State::Success;
}
Event::T1Elapsed => {
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
self.state = State::Refunded;
}
Event::XmrRefundSeenInMempool => {
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
self.state = State::Refunded;
}
_ => panic!("unhandled scenario"),
},
_ => {}
}
}
fn run(&mut self) {
while let Some(event) = self.events.pop_front() {
self.next(event);
}
}
}
impl Persist for Event {
fn persist(&self) {
todo!()
}
}
#[derive(PartialEq, Debug)]
pub enum State {
WatchingForXmrLock,
WaitingForBtcRedeem,
Success,
Refunded,
Aborted,
}
pub enum Event {
// todo: do we simply wait for confirmations are is mempool sufficient?
XmrConfirmed,
// This will contain the s_a allowing bob to build xmr_redeem
BtcRedeemSeenInMempool,
XmrRefundSeenInMempool,
T1Elapsed,
}
pub enum Action {
SignAndBroadcastBtcLock,
BroadcastXmrRedeem,
SignAndBroadcastBtcRefund,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn happy_path() {
let mut state_machine = StateMachine {
state: State::WatchingForXmrLock,
actions: Default::default(),
events: Default::default(),
};
state_machine.events.push_back(Event::XmrConfirmed);
state_machine
.events
.push_back(Event::BtcRedeemSeenInMempool);
state_machine.run();
assert_eq!(state_machine.state, State::Success);
}
#[test]
fn alice_fails_to_redeem_btc_before_t1() {
let mut state_machine = StateMachine {
state: State::WatchingForXmrLock,
actions: Default::default(),
events: Default::default(),
};
state_machine.events.push_back(Event::XmrConfirmed);
state_machine.events.push_back(Event::T1Elapsed);
state_machine.run();
assert_eq!(state_machine.state, State::Refunded);
}
#[test]
fn alice_tries_to_refund_xmr_after_redeeming_btc() {
let mut state_machine = StateMachine {
state: State::WatchingForXmrLock,
actions: Default::default(),
events: Default::default(),
};
state_machine.events.push_back(Event::XmrConfirmed);
state_machine.events.push_back(Event::T1Elapsed);
state_machine.run();
assert_eq!(state_machine.state, State::Refunded);
}
}