Skip to content

Instantly share code, notes, and snippets.

@robertparker
Last active January 21, 2020 05:06
Show Gist options
  • Save robertparker/b5c9df8850eaada0657f5b9ed5bbb1f3 to your computer and use it in GitHub Desktop.
Save robertparker/b5c9df8850eaada0657f5b9ed5bbb1f3 to your computer and use it in GitHub Desktop.

Why Octal Zeros Endanger Democracy

On Macbooks atop plastic tables, Dartmouth seniors freely register to vote in the 2020 primary, the culmination of a long legal battle with New Hampshire state officials who sought to limit liberal college voters in a swing state. A few whoops, fueled by adrenaline and catharsis, filter through the crowd as they hit “Submit.”

Soon after, every voter application is rejected.

This time, there is no political machine to blame. This happened because of something far more opaque: a software bug.


Just like votes, every character counts. I ran into an issue building out a Ruby wrapper for the Rocky API. The API drives RockTheVote's voter registration form, and it also allows new developers to building their own custom voter registration applications.

What is an API? An API wrapper? APIs are access points that let programs exchange information, without needing to know much about each other. An API wrapper is a package of code that lets a developer interact with an API in the programming language of their choice.

Think about a restaurant menu: it allows a chef and customer to exchange food for money, without having to know each other’s names. A menu is an API! In this way, its wrapper would be the menu translated from English to Spanish.

The API has a method, get_state_requirements that lets a user access the relevant state voting requirements for a specific zip code.

Why are voting requirements different by state? Voting requirements have never been clearly defined. Since the "right to vote" is not explicitly stated in the U.S. Constitution except in amendments, it tends to be legislated as "the right to not to be discriminated against while voting." This means that states have considerable latitude to define their own voting requirements, unless those requirements are barred by the federal government as discrimination.
Why do different state voting requirements matter? Voting regulations vary wildly state by state, and these can directly affect turnout. In 2019, Wisconsin state officials gave 230,000 voters thirty days to respond via mail to confirm whether they had changed addresses. After 30,000 responses, the state purged 200,000 voters from their voter rolls - disproportionately voters in metropolitan areas like Milwaukee and Green Bay. Historically, state tactics to limit voter registration and turnout were so irresponsible that the 1965 Voting Rights Act required thirteen states (all Southern) to receive approval from the federal government before changing any local voting law.

Here is some code using the Ruby API wrapper, which aims to look up voter registration requirements for Dartmouth's zip code, 03755.

Code:

require 'rockthevote'
api_wrapper = Rocky::Client.new()
api_wrapper.get_state_requirements(home_zip_code: 03755, lang: "en")

Response:

=> {"message"=>"Invalid ZIP code"}

This is a serious bug: 03755 is a valid zip code. If a developer used this wrapper in their application, then their voter registration app might not actually sign up voters. Why is this happening?

The problem is how Ruby reads numbers starting with zero.

How do we know this is not a problem with the API itself, and not the Ruby wrapper? We can confirm this by accessing the API without Ruby. A typical way to do this is by using a command-line tool called curl.

Code:

curl -X GET -d home_zip_code=03755 -d lang=en https://register.rockthevote.com/api/v4/state_requirements.json

Response:

{
  "message": "New Hampshire law does not allow you to use this form to register\nto vote.  For information about registering to vote, contact your town or\ncity clerk.  Contact information for town and city clerks are listed at\nhttp://www.state.nh.us/sos/clerks.htm.  You can also contact the Secretary\nof State at 603-271-3242 or visit http://www.state.nh.us/sos/electionsnew.htm."
}

If this problem were related to the API, we would have expected the same "Invalid ZIP code" response as above.

Let's print out 03755 in three different ways and see how Ruby evaluates it:

puts 03755
=> 2029

puts 3755
=> 3755

puts '3755'
=> 3755

puts is a basic operation in Ruby - it prints out what you type in. In most cases, Ruby does this without interpretation. But with 03755, Ruby is interpreting that number as an octal (base-8), and then translating it to the decimal format (base-10) when printing.

What is base-8?
Why do computers use base-8?

The reason is because the 0 prefix is an escape character for Ruby, that tells the language to convert a number to octal. Similar escape characters exist for binary (base-2, prefix 0b) and hexadecimal (base-16, prefix 0x). For instance, here is how we write the number sixteen in binary and hexadecimal:

puts 0b1111
=> 16

puts 0x10
=> 16

0x and 0b are much better escape characters, because they can't be confused for numbers. The octal zero is the escape character that causes much confusion in programming, precisely because most of us never intend to use octal when we prefix a number with zero.

The octal zero convention pre-dates Ruby. It was first implemented in in the 1960s in the programming language B. This convention was then carried forward into C. C is a ubiquitous programming language -- the Mac OS operating system is written in C, and most computer science majors are taught C in classes. It also happens to be Ruby's parent language.

Why was the octal zero first implemented in C?
What does it mean that C is Ruby's parent language?

However, in recenty years In fact, the octal zero has been recently outright banned by other programming languages. For instance:

Python 3:

print(03755)
=> SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers

Javascript ECMA 6:

console.log(03755)
=> Uncaught SyntaxError: Octal literals are not allowed in strict mode.

Ruby, a language whose community is neither prescriptive nor precise, offers no such warnings.

While one could argue that this is a problem that should be fixed inside Ruby itself, the most convenient thing to do is to throw an exception in my wrapper.

client.get_state_requirements(home_zip_code: 03755, lang: "en")
=> ArgumentError (home_zip_code must be a string.)

Now, finally, when a developer decides to properly use the wrapper and represent the zip code as 03755 (a string), they will get:

client.get_state_requirements(home_zip_code: "03755", lang: "en")
=>  {
  "message": "New Hampshire law does not allow you to use this form to register\nto vote.  For information about registering to vote, contact your town or\ncity clerk.  Contact information for town and city clerks are listed at\nhttp://www.state.nh.us/sos/clerks.htm.  You can also contact the Secretary\nof State at 603-271-3242 or visit http://www.state.nh.us/sos/electionsnew.htm."
}

Success! It turns out the state of New Hampshire does not allow online voter registration!

On to the next bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment