Non-Interference Until Close: Secure Winner Disclosure in Fixed-Bid Auctions (NuSMV)¶
Description of the Case Study¶
In this case study, we model a real-world bidding system adopted from [Arden12]. The bidding service accepts a fixed, predetermined number of bids during an active bidding phase. All bids are processed and compared internally, and no user-visible indication of the winner, or any information that could allow users to infer the winner, is released before the bidding phase ends and all bids have been received.
The intended security requirement is a simplified non-interference property with declassification at the moment the auction closes. Concretely, throughout the bidding phase the observable outputs must be independent of the bid values (no premature leakage); only when the auction ends is a single piece of information, the final winner, declassified and made observable. This ensures that intermediate outputs cannot influence participant behavior or reveal partial outcomes, while still allowing the correct winner to be announced once the process is complete.
This case study evaluates four versions: three are satisfiable and one is unsatisfiable.
The NuSMV model(s)¶
MODULE main
VAR
    -- Determines if bidding is still occurring
    bidding : boolean;
    -- Current proccessed bid
    curr_bid : 1..4;
    -- Potential winner while still bidding
    potential : 0..3;
    -- Final winner  
    winner : 0..3;
    
    max_bid : 2..6;
    
    -- Bids
    bid_one : 3..4;
    bid_two : 2..3;
    bid_three : 5..6;
        
ASSIGN
    -- Initial values
    init(bidding) := TRUE;
    init(curr_bid) := 1;
    init(potential) := 0;  
    init(winner) := 0;
    init(max_bid) := 3;
    init(bid_one) := 4;
    init(bid_two) := 3;
    init(bid_three) := 6;
    
    next(bid_one) := bid_one;
    next(bid_two) := bid_two;
    next(bid_three) := bid_three;
    -- Updates bidding variable when bidding is done
    next(bidding) :=
        case
            (bidding) & (curr_bid = 4): FALSE;
            (!bidding): FALSE;
            TRUE: TRUE;
        esac;
    
    -- Winner revealed if bidding is over
    next(winner) :=
        case
            (!bidding) : potential;
            TRUE: 0;  
        esac;
    
    -- Updates max bid, if the current bid is greater than max bid then it's the new max
    next(max_bid) :=
        case
            (curr_bid = 1) & (max_bid < bid_one): bid_one;
            (curr_bid = 2) & (max_bid < bid_two): bid_two;
            (curr_bid = 3) & (max_bid < bid_three): bid_three;
            TRUE: max_bid;
        esac;
    -- Updates potential winner
    next(potential) :=
        case
            (max_bid = bid_one): 1;
            (max_bid = bid_two): 2;
            (max_bid = bid_three): 3;
            TRUE: potential;
        esac;
    -- Increment current bid
    next(curr_bid) :=
        case
            (curr_bid < 4): curr_bid + 1;
            TRUE: curr_bid;
        esac;
MODULE main
VAR
    -- Determines if bidding is still occurring
    bidding : boolean;
    -- Current proccessed bid
    curr_bid : 1..3;
    -- Potential winner while still bidding
    potential : 0..2;
    -- Final winner  
    winner : 0..2;
    
    -- Have to use small range or program will timeout from taking too long
    max_bid : 2..6;
    
    -- This fixes issues with code timing out due to range of bid vars
    -- Note: Cannot do bid_one : 3..3; as that threats bid_one as a constant which tool does not support
    bid_one : 3..4;
    bid_two : 2..3;
   
    
    
ASSIGN
    -- Initial values
    init(bidding) := TRUE;
    init(curr_bid) := 1;
    init(potential) := 0;  
    init(winner) := 0;
    init(max_bid) := 3;
    init(bid_one) := 4;
    init(bid_two) := 3;
    
    
    
    -- Updates bidding variable when bidding is done
    next(bidding) :=
        case
            (bidding) & (curr_bid = 3): FALSE;
            (!bidding): FALSE;
            TRUE: TRUE;
        esac;
    
    -- Winner revealed if bidding is over
    next(winner) :=
        case
            (!bidding) : potential;
            TRUE: 0;  
        esac;
    
    -- Updates max bid, if the current bid is greater than max bid then it's the new max
    next(max_bid) :=
        case
            (curr_bid = 1) & (max_bid < bid_one): bid_one;
            (curr_bid = 2) & (max_bid < bid_two): bid_two;
            TRUE: max_bid;
        esac;
    -- Updates potential winner
    next(potential) :=
        case
            (max_bid = bid_one): 1;
            (max_bid = bid_two): 2;
            TRUE: potential;
        esac;
    -- Increment current bid
    next(curr_bid) :=
        case
            (curr_bid < 3): curr_bid + 1;
            TRUE: curr_bid;
        esac;
MODULE main
VAR
    -- Determines if bidding is still occurring
    bidding : boolean;
    -- Current proccessed bid
    curr_bid : 1..5;
    -- Potential winner while still bidding
    potential : 0..4;
    -- Final winner  
    winner : 0..4;
    
    -- Have to use small range or program will timeout from taking too long
    max_bid : 2..6;
    
    -- This fixes issues with code timing out due to range of bid vars
    -- Note: Cannot do bid_one : 3..3; as that threats bid_one as a constant which tool does not support
    bid_one : 3..4;
    bid_two : 2..3;
    bid_three : 4..5;
    bid_four : 5..6;
    
    
ASSIGN
    -- Initial values
    init(bidding) := TRUE;
    init(curr_bid) := 1;
    init(potential) := 0;  
    init(winner) := 0;
    init(max_bid) := 3;
    init(bid_one) := 4;
    init(bid_two) := 3;
    init(bid_three) := 5;
    init(bid_four) := 5;
    
    
    -- Updates bidding variable when bidding is done
    next(bidding) :=
        case
            (bidding) & (curr_bid = 5): FALSE;
            (!bidding): FALSE;
            TRUE: TRUE;
        esac;
    
    -- Winner revealed if bidding is over
    next(winner) :=
        case
            (!bidding) : potential;
            TRUE: 0;  
        esac;
    
    -- Updates max bid, if the current bid is greater than max bid then it's the new max
    next(max_bid) :=
        case
            (curr_bid = 1) & (max_bid < bid_one): bid_one;
            (curr_bid = 2) & (max_bid < bid_two): bid_two;
            (curr_bid = 3) & (max_bid < bid_three): bid_three;
            (curr_bid = 4) & (max_bid < bid_four): bid_four;
            TRUE: max_bid;
        esac;
    -- Updates potential winner
    next(potential) :=
        case
            (max_bid = bid_one): 1;
            (max_bid = bid_two): 2;
            (max_bid = bid_three): 3;
            (max_bid = bid_four): 4;
            TRUE: potential;
        esac;
    -- Increment current bid
    next(curr_bid) :=
        case
            (curr_bid < 5): curr_bid + 1;
            TRUE: curr_bid;
        esac;
MODULE main
VAR
    -- Determines if bidding is still occurring
    bidding : boolean;
    -- Current proccessed bid
    curr_bid : 1..4;
    -- Potential winner while still bidding
    potential : 0..3;
    -- Final winner  
    winner : 0..3;
    
    -- Have to use small range or program will timeout from taking too long
    max_bid : 2..6;
    
    -- This fixes issues with code timing out due to range of bid vars
    -- Note: Cannot do bid_one : 3..3; as that threats bid_one as a constant which tool does not support
    bid_one : 3..4;
    bid_two : 2..3;
    bid_three : 5..6;
ASSIGN
    -- Initial values
    init(bidding) := TRUE;
    init(curr_bid) := 1;
    init(potential) := 0;  
    init(winner) := 0;
    init(max_bid) := 3;
    init(bid_one) := 4;
    init(bid_two) := 3;
    init(bid_three) := 6;
    -- Updates bidding variable when bidding is done
    next(bidding) :=
        case
            (bidding) & (curr_bid = 4): FALSE;
            (!bidding): FALSE;
            TRUE: TRUE;
        esac;
    -- UNSAFE: Winner is revealed early before bidding ends
    next(winner) :=
        case
            (bidding) : potential;  -- Unsafe: Winner updates while bidding is still happening
            (!bidding) : potential;  -- Keeps the final winner
            TRUE: 0;  
        esac;
    -- Updates max bid, if the current bid is greater than max bid then it's the new max
    next(max_bid) :=
        case
            (curr_bid = 1) & (max_bid < bid_one): bid_one;
            (curr_bid = 2) & (max_bid < bid_two): bid_two;
            (curr_bid = 3) & (max_bid < bid_three): bid_three;
            TRUE: max_bid;
        esac;
    -- Updates potential winner
    next(potential) :=
        case
            (max_bid = bid_one): 1;
            (max_bid = bid_two): 2;
            (max_bid = bid_three): 3;
            TRUE: potential;
        esac;
    -- Increment current bid
    next(curr_bid) :=
        case
            (curr_bid < 4): curr_bid + 1;
            TRUE: curr_bid;
        esac;
The HyperLTL formula¶
This requirement can be specified as simplified non-interference with declassification (NI-D) as follows:
Forall A . Forall B . G((bidding[A] & bidding[B]) -> (winner[A] = winner[B]))