ZeroNet Blogs

Static ZeroNet blogs mirror

Notes on Rocket Fuel Mixtures

- Posted in Burning Cold by with comments

I've been experimenting with rocket fuel mixtures for "r-candy" (KNO3 + table sugar) rockets. The canonical mixture is 60/40 by weight (always oxidizer first), or 65/35 for less pure KNO3, though that is known to give far worse Isp.

While a 60/40 mixture is highly efficient, it can have problems with combustion stability at high thrust values. Since thrust is varied by changing the size of the bore and the ratio Ab/h (burn surface vs length), it can become impractical to have very high thrust engines since they would end up being very wide, and thus be inefficient anyway - so it can be worth adding catalyst or intensifier chemicals to those engines.

I tried two intensifiers: Fe(OH)3 (rust) and Al2O3 (aluminum oxide). Both provided between 10 and 20 percent additional thrust, but caused some combustion instability and a very marked decrease in efficiency.

The control engine burned for four seconds and produced a normal thrust curve: large ignition thrust, 4.5 seconds of sustained thrust, and a 0.5 second burnout taper.

My first mixtures were 55% oxidizer, 35% fuel, and 10% intensifier by weight (55/35/10).

Iron Oxide

The iron oxide mixture exploded the first time due to being improperly mixed. Its second test produced a huge initial thrust for about 0.22 seconds, about one second of sustained low thrust, and then exploded due to a crack in the grain. The third test produced a thrust profile very similar to the control engine with a much larger initial thrust, a shorter (only 3 second) sustained burn at about 18% more thrust, and then instead of a taper-off another spike which looked like the result of a small explosion.

Aluminum Oxide

My aluminum oxide mixture worked well all three times I fired it with none of the explosive properties of the iron oxide mixture. They produced a much more sedate curve; rather than an initial massive acceleration with a long sustained burn, they fired for a total of between 2 and 2.5 seconds at between 20% and 5% more thrust than the sustained thrust of the control engine. There were, however, occasional dips to below 50% nominal control thrust, accompanied by an expellation of sparks and very dark smoke. I don't know why this is; I'll be investigating further in a later post.

These engines did have a tendency to fail to ignite, but I think that can be solved with a better ignition system.


Overall, the 10% aluminum oxide fuel was much more successful; it was easier to use without risk of CATO and produced significantly more thrust; however, it had only 60% of the total impulse.

Mastodon Social

- Posted in Burning Cold by with comments

Mastodon Social is pretty cool. It's a microblogging server that's server-to-server compatible with GNUSocial and works with the whole fediverse. I'm probably going to end up leaving Twitter permanently.

Paper Rocket Engines

- Posted in Burning Cold by with comments


I've been interested in rocket engines for some time now, especially the design of combustion chambers. I'm no physicist, and I've never claimed to be, nor do I have a budget. On the other hand, there's no way to test out one's understanding of combustion but to try it - so I decided to build a small rocket engine.

A First Attempt

The easiest way to build an engine is simply to roll up a tube of paper and fill it with butane, and light it. This will produce a little tiny bit of thrust out both ends. Capping one end directs this thrust out the other end, and boom. A tiny, pointless rocket engine.

I built a number of these, with increasing sophistication. I found that, with the addition of two spools from Scotch tape reels to reinforce the ends, I can create a double-walled chamber that won't burn my hands. Here's how it works.

Take four index cards and align them. Then roll them up and slot them into the slot that runs around the circumference of the tape roll. Place the other tape roll on top and use Scotch or duct tape to secure them, with a pressure seal! You can blow into either end with one hand over the other to see if the seal is good.

Now cap one end with some layered tape. Duct tape works, but Scotch lets you see the combustion inside. Then take a fifth index card and wrap it around the outside, securing it with tape. The air between the inner and outer wall will absorb radiated heat and protect your hand.

Hold the engine with the open side up and place a lighter upside-down with the head inside the engine. Depress the release valve without igniting and allow butane gas to enter the engine. It's heavier than air, so it will fall to the bottom.

Now flip the engine so the open end is just a few degrees down from horizontal and hold the lighter two to three centimeters from the opening, and ignite it. Whoosh! Thrust.

This simple design can be improved by adding a nozzle; I have yet to optimize a design. I am also working on fuel feeding from the capped end so that the engine can produce an appreciable impulse.

Session Types

- Posted in Burning Cold by with comments

What are session types?

Session types are, essentially, a technique for using a rich type system like that of Rust or OCaml to express semantic meaning and prevent the representation of certain kinds of illegal states, especially with respect to causality.

What is the use-case?

Let's take the somewhat contrived example of a system representing packaging and shipping boxes. I want to create a Package datastructure, pack data into it, close it (preventing adding data), address it, and then ship it. It makes no sense to send an un-addressed Package, or to insert data into a closed one.

We could represent this like this:

struct Package<T> {
    is_closed: bool,
    is_addressed: bool,
    data: T

We would then have lots of runtime typechecking of whether boxes submitted for shipping were addressed and such, which costs time and is error-prone. Wouldn't it be nice if the compiler could enforce this?

How is it implemented?

The easiest way to implement this is with three different types: an OpenPackage, a ClosedPackage, and an Addressed Package.

struct OpenPackage<T> {
    pub contents: T

Crucially, the contents field is pub. You can poke and prod and change that data all you want.

This has a few capabilities: pack, which takes control of whatever is supposed to go in the package and creates an OpenPackage around it, unpack, which destroys the OpenPackage and gives back its contents, and finally close, which converts the OpenPackage to a ClosedPackage.

impl <T: Sized> OpenPackage<T> {
    fn new(contents: T) -> Self {
        OpenPackage::<T> {contents: contents}

    fn pack(contents: T) -> Self {
        println!("\tPut some data in a package.");

    fn unpack(self) -> T {
        println!("\tPackage unpacked.");

    fn close(self) -> ClosedPackage<T> {
        println!("\tPackage closed.");

This leads naturally to the ClosedPackage struct:

struct ClosedPackage<T> {
    contents: T

Very similar, but without the pub attribute. This means that a ClosedPackage isn't in danger of having its contents manipulated in any way.

ClosedPackages can be opened again, yielding an OpenPackage, or addressed, creating an AddressedPackage.

impl <T: Sized> ClosedPackage<T> {
    fn new(contents: T) -> Self {
        ClosedPackage::<T> { contents: contents }
    fn open(self) -> OpenPackage<T> {
        println!("\tPackage opened.");
    fn address(self, address: String) -> AddressedPackage<T> {
        println!("\tAddressed a closed package.");
        AddressedPackage::new(self.contents, address)

Finally, the AddressedPackage struct represents one with a specified destination. I used a String for the address here, but it would be trivial to create a generic version.

struct AddressedPackage<T> {
    contents: T,
    pub address: String

To understand the access controls here, just think of a physical package. The address is on the outside; anyone can read it or cross it out with a sharpie. The contents, however, are sealed away.

This struct can be turned back into a ClosedPackage by receiveing it:

impl <T: Sized> AddressedPackage<T> {
    fn new(contents: T, address: String) -> Self {
        AddressedPackage::<T> { contents: contents, address: address }
    fn receive(self) -> ClosedPackage<T> {
        println!("\tPackage recieved.");

Finally, I created an example function to "send" the package somewhere.

fn send_package<T: Sized+std::fmt::Display>(package: AddressedPackage<T>) -> Result<String, String> {
    // Save the address.
    let address = package.address.clone();

    // Destroy the package to get at the contents
    let contents = package.receive().open().unpack();
    println!("Destination recieved: {}", contents);
    // Success! Theoretically this function could fail, but not with this implementation.
    Ok(format!("Sent package to {}", address))

fn main() {
    // Make a box and then destroy it.
    let contents: String = "Here is some data.".into();
    let package = OpenPackage::pack(contents);
    // The package owns its contents.
    // println!("{}", contents); is invalid.
    println!("{}", package.unpack());

    // Now, make a box, close it, and address it.
    let contents: String = "Here is some MORE data.".into();
    let package = OpenPackage::pack(contents);
    let closed_package = package.close();
    // We now can't unpack the package to get to its contents. This is an error:
    // let contents = closed_package.unpack();
    // because ClosedPackage doesn't have .unpack()
    // Also, package is no longer valid, so no duplication can occur.
    // Finally, we can't send_package() this package; we have to address it.
    let addressed_package = closed_package.address("6902 East Pass, Madison, WI".into());
    // Now we can send the package.
    println!("{:?}", send_package(addressed_package));

This ends up printing out:

$ ./session_types
    Put some data in a package.
    Package unpacked.
Here is some data.
    Put some data in a package.
    Package closed.
    Addressed a closed package.
    Package recieved.
    Package opened.
    Package unpacked.
Destination recieved: Here is some MORE data.
Ok("Sent package to 6902 East Pass, Madison, WI")

I hope this has been an instructive journey through the world of session types and a good example of why they are useful.

In the real world, the Rust crate hyper makes heavy use of session types to ensure the integrity of HTTP requests and responses.

I ❤️ ZeroNet

- Posted in Burning Cold by with comments


I've been interested in alternatives to the traditional Internet infrastructure for a number of years. I've worked on and used Tor hidden services, FreeNet, I2P, and IPFS, as well as (obviously) ZeroNet. Of these, ZeroNet is the clear winner in my opinion. In conjunction with Tor and IPFS, ZeroNet could truly change the way people use the 'net.

But why?


Painless Installation is a big one. Installing IPFS or FreeNet is a nightmare. The use of a web-based portal similar to the one used by IPFS, in conjunction with simple and quick-to-install bundles for most OSes, makes ZeroNet much

SSO Everywhere via services like zeroid.bit is a huge factor. It provides a totally transparent identity interface. There could certainly be improvements, like having passphrases for saved keys so they could more safely be backed up, but those can be added on by users or implemented in the future.

Good Apps are really important too. IPFS and the Tor hidden service ecosystem lack good tutorials, example apps, and working, production-class social networks, which exist and are well adopted in ZeroNet.


The only disadvantage I can see so far is that ZeroNet is relatively heavyweight, requiring Python and thus precluding it from mobile access. However, the web-UI approach makes this more manageable.

So, yeah. I ❤️ ZeroNet.