I've done this for a commercial PSP racing game, which worked both over an ad hoc network, and via a wireless hotspot. (Or to a static server on the Internet, if desired)
Because of point 2 the system needn't be complex with regards to optimization
In my experience, this is not true. Wireless devices (especially small portable ones) are not like computers with wired network connections -- smartphones and wireless game consoles tend to have slow network interfaces for game purposes.
Don't get me wrong -- their throughput is usually good (that is, the amount of data per second; great for streaming movies or etc), but the latency on delivery of a particular packet can be extremely bad, and can be so highly variable that it's difficult to even estimate how long any individual packet will take to deliver. This variation becomes even worse as more wireless devices are packed together into one general area, as their signals start to interfere with each other. As a result of this, quite a lot of my time was spent in reducing the number of packets that needed to be sent, so we would have fewer packet collisions. (Note that this is somewhat less of a problem in the case where a powered network hotspot is involved, rather than having the devices talk to each other directly over an ad hoc network)
As an example of this sort of optimisation, our racing game took place in a world which had traffic lights. Thousands of them. And we needed to make sure that their signals were in synch between all the players in a network session. Rather than try to send packets around telling everyone which lights were in what state, we defined a static schedule for all of the traffic lights, and then just made sure that all the clients agreed on the current "game time". Since they all knew the game time, and all the traffic lights' states could be determined from the game time, we synchronised all that state data without actually sending any special data. This one change made a huge difference for our networking performance.
What that said, establishing a reliable clock synch between multiple wireless devices (with highly varying ping times due largely to packet loss) was a huge challenge. Happy to talk more about that if you've an interest.
Each client can be an authoritative source of data about himself and
his immediate environment (for example bullets.)
This is what we did, and it worked well for us in our situation (cars). The problematic part, of course, is when an object stops being closer to player 'a' than to player 'b', and its ownership therefore transfers from one player to another.
This is actually a surprisingly complex negotiation between players, where game 'a' proposes to game 'b': "I think this object is closer to you. You should take control of it." And then game 'b' may either accept, or may decline, based upon its own view of the situation. Differences in the perceived game state between 'a' and 'b', and the change in time between when the request and response are sent and received makes this a particularly nasty little negotiation to get reliable, and it can easily degenerate into a game of "hot potato", with object ownership bouncing around continually between multiple players. And even when it does work properly, when viewed from the vantage point of game 'c', there's often a small visual discontinuity when an object switches from the ownership of one player to the ownership of another, just due to the different ping times that game 'c' has to the other two players.
My intuition is that this sort of "object ownership" approach is likely to be too cumbersome for small, short-lived objects like bullets. We used it for traffic cars and AI racers, which tended to live in the simulation for a relatively long time. It seems like a more performant approach, if you're willing to trust the clients, would be to have each player's game own their position and their projectiles, and declare when that player has been hit by someone else's projectile. (So as "game A", I'm responsible for saying where player A and player A's projectiles are, but player B is responsible for saying whether I've hit player B). With some good dead-reckoning, you should be able to get pretty reasonable behaviour out of a system like this.