Jekyll2021-04-26T05:19:52+00:00https://karla.io/feed.xmlkarla.ioEquity taxes: Putting it all together2021-04-26T00:00:00+00:002021-04-26T00:00:00+00:00https://karla.io/2021/04/26/putting-it-all-together<p>Now that we’ve covered <a href="/2020/07/16/source-taxation.html">source taxation</a>,
<a href="/2020/08/31/us-taxes.html">US taxation</a>, and <a href="/2020/12/12/au-taxes.html">Australian taxation</a>,
we can finally talk about all the exciting edge cases you hit when trying to
file equity-related taxes after moving internationally. Some of these problems
are from lack of clarity in one or the other of the domestic tax systems, but
most come about from the quirks of how domestic tax systems interact with
international tax law.</p>
<p>These edge cases are necessarily quite tied to the countries in question –
the US and Australia in this case – but I hope that they’re illustrative
enough to show you where you might need to be careful with other countries.
More so for this post than for the others, we start to reach the edge of what’s
clearly defined in the law, and move into “this behavior is undefined, but a
reasonable way to think about it might be…”. This leaves a lot of scope for
interpretation, and as ever, I’m not a tax professional. If something seems off,
<a href="mailto:hello@karla.io">send me an email</a>, I’d love to hear about it!</p>
<h3 id="-tax-residency">🏠 Tax residency</h3>
<p>One of the first problems you run into when filing taxes internationally is
figuring out where exactly you actually <em>are</em> a tax resident. This question,
oddly enough, is nearly completely decoupled from the question of whether you’re
a resident for immigration purposes, so it’s not enough to look at what your
passport says. Reasoning from first principles, you might think that you could
only be a tax resident of one country in any given year, and that you’d be a
tax resident of whichever country you spent the majority of the year in. While
this is often true, there are some sharp edges you need to watch out for.</p>
<h3 id="-tax-residency-the-us">🇺🇸 Tax residency: the US</h3>
<p>In the eyes of the IRS, and so the US federal system, if you’re a US citizen or
permanent resident, you’re out of luck: you’re required to file US tax returns
and pay US tax regardless of the country in which you currently live. However,
if you’re not, things get more complex. The year that you move to the US tends
to be the simplest, and assuming you haven’t spent time in the US previously,
the following applies:</p>
<ul>
<li>If you spend less than 31 days of your first year in the US, you are not a tax resident</li>
<li>If you spend more than 183 days of your first year in the US, you are a tax resident</li>
<li>If you spend between 31 and 183 days of your first year in the US, you can choose to be a tax resident, or a “dual status alien”,
i.e. to file a resident return that only counts income earned after a certain date. The IRS calls choosing between these options
the <a href="https://www.irs.gov/individuals/international-taxpayers/alien-tax-status-first-year-choice">First Year Choice</a></li>
</ul>
<p>Once you reside in the US for a full tax year, with the exception of certain
visa classes (e.g. the J-1, typically used for student internships), you’ll
become a US tax resident until you leave, by virtue of the <a href="https://www.irs.gov/individuals/international-taxpayers/substantial-presence-test">Substantial Presence Test</a>. This test states that you
must be a US tax resident if you both spend more than 31 days in the US in a
given year, and if the sum of days you spent in the US in the current year, the
previous year (divided by three), and the year before that (divided by six) is
greater than 183 days.</p>
<p>However, this test can catch you out in the year that you leave the US. Assuming
that you have spent the vast majority of your time in the US in previous years,
spending 31 days in the US the year after you move is enough to qualify you as
a resident under the Substantial Presence Test (since 31 + 365/3 + 365/6 > 183).
This can be a problem if you’re returning to the US for business meetings on a
somewhat regular cadence.</p>
<p>In that case, you might need to rely on the <a href="https://www.irs.gov/individuals/international-taxpayers/closer-connection-exception-to-the-substantial-presence-test">Closer Connection Exception</a> to the Substantial
Presence Test. This exception is pretty subjective, but at a high level relies
on you proving that you have transitioned your tax residency to another country,
and that you have a more material connection to that country than to the United
States.</p>
<h3 id="-tax-residency-australia">🇦🇺 Tax residency: Australia</h3>
<p>Australia, like the US, is quite clingy with taxation rights over its citizens,
and has <a href="https://www.ato.gov.au/Individuals/coming-to-australia-or-going-overseas/Your-tax-residency/">a number of tests</a>
that compel someone to be a resident for tax purposes:</p>
<ul>
<li><a href="https://www.ato.gov.au/Individuals/coming-to-australia-or-going-overseas/in-detail/residency/residency---the-resides-test/">Resides test</a>: if you reside in Australia, in the same vague way that the
US defines a closer connection (where you physically are, your intention,
your family, etc), you are a tax resident.</li>
<li><a href="https://www.ato.gov.au/Individuals/coming-to-australia-or-going-overseas/In-detail/Residency/Residency---the-domicile-test/">Domicile test</a>: if your permanent home is in Australia, you are
a tax resident. In practice, if you are born in Australia, this test will
compel you to be a tax resident unless you choose to permanently migrate to
another country.</li>
<li><a href="https://www.ato.gov.au/Individuals/coming-to-australia-or-going-overseas/In-detail/Residency/Residency---the-183-day-test/">183 day test</a>: if you are present in Australia for more than
183 days in a given year, you are a tax resident unless you have a usual place
of abode outside of Australia.</li>
</ul>
<p>Meeting any of these will cause you to become an Australian tax resident.</p>
<h4 id="️-tax-residency-doubly-resident">👯♀️ Tax residency: Doubly resident</h4>
<p>You’ll note that it’s possible to satisfy both countries’ residency conditions
in a single year. For example, if you retain a permanent home in Australia, but
spend more than 183 days in the United States, both countries might consider you
to be a tax resident. I’d recommend avoiding this situation, but if it does come
up, the <a href="https://www.irs.gov/pub/irs-trty/aus.pdf">US/AU Income Tax Treaty</a>
provides tie-breaking clauses for this case, which largely boil down to
“where do you usually sleep”, and “which country is your employer domiciled in”.</p>
<h3 id="-tax-credits">💳 Tax credits</h3>
<p>Once you figure out which country you’re resident or non-resident of for tax
purposes, you still need to figure out which parts of your income will be taxed
in each country. At a high level, a tax treaty between two countries typically
tries to ensure that an individual with tax obligations in both countries pays
tax in at least one country, and where possible, that they aren’t double taxed
on income. Double taxation is usually avoided by ensuring that countries don’t
have overlapping taxation rights on the same income, or where they do, that they
provide a tax credit if tax has already been paid on that income. In Australia,
these credits are referred to as Foreign Income Tax Offsets (FITO for short).</p>
<h3 id="-time-inconsistencies">🕰 Time inconsistencies</h3>
<p>Of course, there’s a problem with this tax credit theory which pops up pretty
immediately: the misalignment of tax years. In the US, the tax year runs with
the calendar year, and returns are generally due in April of the following year.
In Australia, the tax year runs July to June, and returns are typically due in
October. While people from countries with tax years aligned to the calendar are
often surprised by this, the problem is far from unusual, and <a href="https://en.wikipedia.org/wiki/Fiscal_year#Operation_in_various_countries/region">many countries</a> have fiscal years that start
on different dates.</p>
<p>This is problematic, because in Australia and many other countries, you can only
claim a credit for income that would be taxed in Australia, and for tax that
has already been paid. That’s an issue if you sell an asset in say, March.
Without an extension, you’ll need to file your Australian taxes in October of
the same year, but wouldn’t file your US taxes for that transaction until
January of the following year at the earliest, and April typically. This means
that in order to claim a credit, you either need to delay filing your Australian
returns and attempt to file your US returns earlier in the filing window, or you
need to file your Australian returns without the credit, pay both sets of taxes,
then amend your past Australian return to include the US taxes you’ve now paid,
and request a refund from the ATO.</p>
<p>You could, of course, try to claim a foreign tax credit in the US instead in
this situation. Unfortunately though, the tax treaties between two countries
typically only applies to their federal taxes, and not their state taxes. In
many cases, this means that you by design will end up paying the higher of the
two countries’ federal tax rates, and then additionally any state taxes.</p>
<p>However, the ATO is quite kind in this respect, and states that
<a href="https://www.ato.gov.au/law/view/document?docid=ITR/IT2507/NAT/ATO/00001">they consider Californian income taxes</a>
to be a part of the US tax system, and that you can therefore claim them as a
foreign tax credit. The same is not true in reverse – California will not
give you a tax credit for your Australian taxes – so you really want to
find a way to file your US taxes first.</p>
<p>In practice, the easiest way to do this is to pay an accountant to file your
Australian returns on your behalf, as they’re able to apply for filing
extensions until at least March the following year. If you’re dealing with
international tax, it’s also likely worth paying someone to make sure that it’s
being done correctly.</p>
<h3 id="-foreign-tax-credits-and-currencies">💵 Foreign tax credits and currencies</h3>
<p>Assuming you do file your returns in the “correct” order, you’re then likely to
run into problems with identifying what tax was paid when. Your Australian
return for a given financial year will potentially need credits from two
different US tax years, but you can’t just sum up the tax paid in each, since
some of the transactions might count for the previous or next Australian tax
year.</p>
<p>Further, you pay US taxes in US dollars, but Australian taxes in Australian
dollars. Which exchange rate do you use for tax calculations? The rate on the
first or last day of the financial year? The rate on the day you paid your
taxes?</p>
<h4 id="-an-example">📗 An example</h4>
<p>The ATO handles this by opting to do all tax return working in percentages and
Australian dollars. This is best demonstrated by example, so let’s say that you
exercised an NSO that was granted after July 1 2015 and vested in the US, while
you were an Australian resident. The spread between the FMV and the strike price
of the options you exercised was US$10,000, and because of the dates involved,
100% of that spread is taxable at income tax rates in both Australia (residency
taxation) and the US (source taxation).</p>
<p>From the ATO’s perspective, the first thing to work out is the amount of income your
US$10,000 equates to in Australian dollars, a process called <a href="https://www.ato.gov.au/business/foreign-exchange-gains-and-losses/translation-(conversion)-rules/">translation</a>. The ATO is
surprisingly flexible in the rates that they allow, and allows you to choose
between either the actual exchange rate you receive from your bank when the
transaction settles to AUD (if that happens immediately), or their view of the
“spot rate” for that currency. We’ll say that 1 USD purchased 1.300 AUD on the
date of your transaction, per the ATO’s rates, and so you realized income of
AU$13,000.</p>
<p>While that tells us the amount of income you need to put down on your Australia
tax return, it doesn’t indicate the amount of FITO credit that you can claim.
That is calculated by taking the overall percentage tax paid on your US tax
return, and then applying that to the Australian income realized on the sale.</p>
<p>So, let’s say that you also performed other transactions in the US that meant
that overall, your US Federal and Californian income for the year of the
transaction was US$200,000. On this, you paid US$40,000 in income tax federally,
US$10,000 in FICA tax federally, and US$15,000 in Californian tax. Overall, this
means that you paid US$65,000 in tax.</p>
<p>For your Australian return, you would calculate that in that US tax year, you
paid 65,000 / 200,000 = 32.5% tax. Since your transaction was considered to be
AU$13,000 of income, you can claim up to AU$13,000 * 0.325 = AU$4,225 in FITO
credit on your Australian return. <em>However</em>, you can only claim FITO credits up
to the amount of tax that you would pay in Australia. This means that if your
overall tax rate in Australia is only 30%, you can only claim a AU$3,900 FITO
credit.</p>
<p>You apply this same logic to each piece of income that you earn and might be
eligible for FITO credit.</p>
<h3 id="-disagreements-about-the-state-of-assets">😤 Disagreements about the state of assets</h3>
<p>This FITO credit strategy makes a lot of sense for the simple case given above,
and side-steps a lot of questions about when and how currency translation should
be calculated. However, it starts to make a lot less sense when the ATO and the
IRS/FTB disagree on the current state of an asset.</p>
<p>This is particularly likely with pre-2015 options, or with NSOs that are valid
substantially beyond your employment at a company. In those cases, the two
countries have different definitions of what type of events cause an option to
be taxed. If you are granted an option before July 1 2015, and it is considered
not to have transfer restrictions on it, it is considered to be taxed at vest by
the ATO. However, the IRS does not consider this to be a taxing point, so if you
are a US resident at the time, you aren’t obliged to pay Australian taxes, and
so nothing is due. A similar logic applies if you leave your company with
unexercised NSOs, but while living in the US: you hit an Australian taxation
point while not an Australian resident, so no tax is due.</p>
<p>Since you’ve already hit an Australian taxation point, if you subsequently
become an Australian tax resident, the ATO will consider those options to be
CGT assets, and will reset their cost basis on the day that your residency
status changes.</p>
<p>This means that your options are considered a CGT asset in Australia, with a
cost basis based on the FMV of date of your residency status change, but still
an as-yet-untaxed option in the US, with a cost basis of the strike price.</p>
<p>When you later go to do your FITO tax calculations, the amount of income earned
on the asset in each country will be different, which leads to unexpected
behavior.</p>
<p>For example, if the strike price of your option in this situation is US$1, the
FMV on the day you move is US$5, and you later exercise and sell the option for
US$10:</p>
<ul>
<li>The US believes that you have a spread of US$9, and taxes you at income tax
rates. You pay 37% tax on this amount, i.e. US$3.33.</li>
<li>Meanwhile, California believes that you have a spread of US$9, but that it
should be apportioned. If you spent half of the time from grant to exercise in
California, you would have a CA-taxable spread of US$4.50. You pay 8% on
this amount, i.e. US$0.36.</li>
<li>Australia believes that you have a spread of US$5 (because the cost basis of
the options is higher) which at the spot exchange rates equates to AU$6.50.
You pay 47% tax on this amount, i.e. AU$3.055.</li>
<li>In the US federally, Australia calculates that you paid 37% tax. For this, you
get a FITO credit on your AU$6.50 of income of AU$2.405 on your Australian
return.</li>
<li>In California, Australia calculates that you paid 8% tax. However, your income
in California equates to just AU$5.85 at spot exchange rates, which is less
than the income on your Australian return. Since the ATO only grants credits
for tax “<a href="https://www.ato.gov.au/Forms/Guide-to-foreign-income-tax-offset-rules-2020/?anchor=Tax_must_have_been_paid_or_deemed_to_ha#Tax_must_have_been_paid_or_deemed_to_ha">actually paid</a>”,
even though Australia would expect California to tax 100% of the income, you
can only claim a credit on AU$5.85 * 8% = AU$0.468.</li>
<li>This gives you a total FITO credit of AU$2.873, meaning that overall you pay
US$3.33 to the IRS, US$0.36 to the FTB, and AU$0.182 to the ATO. This is
an effective tax rate of 42.55% – lower than the Australian tax rate,
because Australia believes that the cost basis of the option is higher; and
lower than the overall US tax rate, because California apportions income
differently, and does not believe that they have the right to tax all of the
spread.</li>
</ul>
<h3 id="-capital-gains">📈 Capital gains</h3>
<p>As might be obvious, capital gains is where this all starts to get a bit wonky,
and in fact the situation is actually more strange than just the disagreement
mentioned above. Typically, capital gains taxes are only payable in your country
of residence – they’re charged on assets that are capital in nature (i.e.
investments), so aren’t subject to source taxation the way that compensatory
income is.</p>
<p>For Australia and the US (and California), this applies to exercised options.
Once you’ve exercised an option, any subsequent gain is capital, and you’ll only
need to pay tax on that gain in the country (and in the US, the state) you’re
currently a tax resident of.</p>
<p>This is a bit weird! It means that if you exercise an ISO while not a US
resident, you’ll pay AMT, but you’ll never actually pay “normal” tax on that
option, since all other taxes due on ISOs are capital gains taxes. If the “other
country” is Australia, which itself has capital gains taxes, this doesn’t make
much of a difference, but if you were to move to a country with no capital gains
taxes (e.g. <a href="https://nomoretax.eu/living/relocation-to-switzerland/">Switzerland</a>),
this acts as a substantial loophole.</p>
<p>While both those examples have resulted in you paying less tax, not all of the
oddities are favourable. Remember how the FITO credit in Australia is calculated
on the basis of your Australian income? Long term capital gains tax discounts in
Australia are applied by reducing the income you count on your tax return, which
effectively halves the amount of FITO credit you can claim.</p>
<p>For example, let’s say that you pay US tax of US$2 on US$10 of income. In
Australia, that income is AU$13, but is a long-term capital gain, so only
AU$6.50 goes on your return. You pay a rate of 47% on that income, or AU$3.055.
Your tax rate in the US was 20%, but since your income was halved on your
Australian return, you’re only given a FITO credit of AU$1.30. In the end,
you pay AU$1.755 and US$2 of tax, or 33.5% tax. This is higher than the tax
rate that the US charged (20%), or that Australia charged (23.5%), and
<a href="https://www.ato.gov.au/Media-centre/Media-releases/Court-confirms-ATO-position-on-foreign-income-tax-offsets/">represents double taxation on half of your income</a>.</p>
<p>While not the intent of Australia’s FITO credit system, this is the
implementation, and speaks to how the same edge cases that create loopholes also
create traps. It’s important to be aware of these when dealing with
international tax.</p>
<h3 id="-couple-problems">😘 Couple problems</h3>
<p>One final issue you might run into between countries is the question of who
really owns the equity you’re talking about. California is a “community
roperty” state, which means that if you and your partner are married, all assets
earned while married are considered to be owned by the two of you jointly. When
you file your tax return at the end of the year, you file a joint return with
any income that either of you earned on it, and your tax bracket is determined
jointly.</p>
<p>Australia meanwhile is a separate property state, with each individual required
to file their own tax return, and assessed separately.</p>
<p>If you are married when you are granted and vest equity in the US, and then
return to Australia, whose return should the income go on on your Australian
return? In fact, in the US it’s not possible to file with the status “Married
Filing Jointly” while a non-US resident, so whose return should the income go
on on your US federal and Californian returns?</p>
<p>For ESS assets in Australia, the answer is quite clear. Regardless of where the
equity was originally granted, an ESS asset can only have been issued to an
employee of a company and their “associates”. In the case where an asset is
owned by an associate, income from that asset must still be declared on the
employee’s tax return.</p>
<p>For CGT assets in Australia, the answer is more murky, and will depend on the
interpretation of the original grant under Australian law. Since in most cases
this grant will reference only your name and not your partner’s, and since
individual taxation would be the default in Australia, in all likelihood all
income would appear on your individual return. (Definitely check with a
professional if this case applies to you though!)</p>
<p>In the US federally and in California, as far as I can tell, there is no
published answer. Having spent several hours on the phone to the IRS trying to
get an answer to this question, I was told to “ask my accountant”. For our
personal situation, this has meant declaring income only on the individual who
was originally granted the equity’s return, but if this applies to you,
definitely also talk to a professional.</p>
<h3 id="-wrapping-up">🎁 Wrapping up</h3>
<p>That’s hopefully more than you’ve ever wanted to know about international
taxation of equity: the overall design, the interactions with individual
domestic tax situations, and the very strange cases you start to hit when you
put all the pieces together.</p>
<p>I haven’t given a lot of examples in this last post, because they have a
tendency to get very complicated. In fact, as part of the research I’ve done
into this over the years, it’s become so hard to reason about what will happen
in different circumstances that I’ve written several thousand lines of Python
to make sure that I understand all the different edge cases.</p>
<p>If you take anything away from this, I hope that it’s an understanding of just
how time-consuming all of this can be, and how much of it is mired in good
intentions trying to patch together fundamentally inconsistent systems. If
you’re curious to know more about how tax treaties are designed to take these
situations into account, I recommend a read through “The treatment of
employee stock-options” on page 263 of <a href="https://www.oecd.org/berlin/publikationen/43324465.pdf">the OECD commentary on the model tax convention</a>, which goes into detail on the various ways in which problems can
arise from deferred taxation of equity.</p>
<p>Thanks to Vivian Ho for encouraging me to finish off this series, and to Wing Ho
for suggesting extra areas to include! 😊</p>Now that we’ve covered source taxation, US taxation, and Australian taxation, we can finally talk about all the exciting edge cases you hit when trying to file equity-related taxes after moving internationally. Some of these problems are from lack of clarity in one or the other of the domestic tax systems, but most come about from the quirks of how domestic tax systems interact with international tax law.Equity taxes: the Australian perspective2020-12-12T00:00:00+00:002020-12-12T00:00:00+00:00https://karla.io/2020/12/12/au-taxes<p>We’ve <a href="/2020/08/31/us-taxes.html">talked a little about US equity taxes</a>, but now let’s turn to a different
country, and discuss how those same taxes are applied in Australia. If you’re
not subject to Australian taxation this might sound irrelevant to you, but we’re
going to need two countries to talk about the final details of international
tax, so I’d encourage you to stick with it. As ever, I’m not a tax professional:
if you spot something that seems off here, I’d love to
<a href="mailto:hello@karla.io">hear from you</a>.</p>
<h3 id="-an-overview-of-australian-taxes">🇦🇺 An overview of Australian taxes</h3>
<p>Overall, the Australian tax system is much less complicated than the US system.
While some taxes are levied at a state level (GST, land tax, and payroll tax being
among the most common), none of these taxes need to be considered by individuals
for income or capital gains. Because of this, the amount of tax you’ll pay on equity is
the same, regardless of where in Australia you live. This simplifies our discussion,
since we’ll only need to talk about how equity is taxed under one system.</p>
<p>Like the US, Australia’s income and capital gains tax system is progressive,
with tax rates ranging from 0% to 45%. Australia has an additional 2% Medicare
tax levied on most taxpayers, on all income, taking the top marginal tax rate to
47%. As in our discussion of US taxes, we’ll assume that all earnings are in the
top tax bracket for simplicity, though the actual amount of tax you would need
to pay is lower. (Note that Australia has a separate Medicare levy <em>surcharge</em>,
chargeable if you earn over a certain amount and don’t have private health
insurance. The cost of insurance isn’t related to your income and is
approximately fixed, so we’ll leave the levy out, since in the limit you pay for
private health insurance and its effect would round to $0).</p>
<p>Simplifying our discussion further, unlike the US tax system, the Australian
system does not allow filing joint returns. While the amount your spouse earns
might affect your eligibility for social welfare, it will not affect the amount
of tax you pay. All individuals who earn an income must file their own returns.</p>
<h3 id="-capital-gains-tax">📈 Capital gains tax</h3>
<p>The implementation of capital gains taxes is quite different in Australia to
the US. In Australia, capital gains on assets held for less than 12 months count
as normal income, and are subject to the same 47% tax that all other income is.
Capital gains on assets held for at least 12 months are eligible for a “capital
gains tax discount”. While taxpayers must still include their gains in their
normal income, they only include <em>half</em> the value of the gains, provided the
asset was held for at least 12 months. For example, if you were to purchase a
car for $1000, and sell it at least a year later for $2000, you would declare an
income of $1000/2 = $500 on your tax return. That halved amount would then be
taxed at 47%, as with all income, yielding an effective tax rate of 23.5%.</p>
<h3 id="-rsus-in-australia">💼 RSUs in Australia</h3>
<p>The issue of whether employee equity is capital in nature – i.e. the
result of an investment decision – or compensatory – i.e. part of
your income – has also been problematic in Australia. As in the US, the
Australian Tax Office (ATO) addressed the issue by creating a particular set of
rules for employee equity, called the Employee Share Scheme (ESS) provisions.
Equity granted to employees is taxed under these provisions once, and thereafter
is subject to the normal CGT rules that apply to non-employee equity.</p>
<p>As with ISOs in the US, ESS assets are designed to receive favourable tax
treatment. While gains from ESS assets can’t be excluded from your tax return
(except in cases where the company they are granted for is very small), the ESS
provisions allow you to defer paying taxes until a later date, when your equity
becomes liquid. This is designed to prevent employees from incurring a tax
liability before they’re able to sell the underlying asset.</p>
<p>In practice, this is done with two tests: a test for “real risk of forfeiture”,
and another for “genuine disposal restrictions”. You can only be taxed on an
asset if you no longer have a real risk of forfeiting the asset, and if there
are no genuine disposal restrictions that prevent you from selling or gifting
the asset to another party.</p>
<p>When you’re granted equity, under Australian law, it is immediately created and
given to you. However, if there is a vesting schedule on that equity, you’re
considered to have a “real risk” of forfeiting that asset in the future, for
example, if you were to leave the company prior to some or all of the equity
vesting. This “real risk” disappears once you vest the equity, since it is now
yours, regardless of whether or not you choose to remain at the company.</p>
<p>If your equity is in a public company, you’ll generally be able to sell your
equity immediately or shortly after it vests, and so it will be taxable as
normal income at that point. However, if your equity is in a private
company, there may well be provisions preventing you from selling your
equity, even after it has vested. These provisions can constitute a “transfer
restriction”, and can allow you to defer taxation until the point at which you
can sell the equity.</p>
<p>It’s important to note that you’ll need to pay tax once your real risk of
forfeiture disappears, and your disposal restrictions (if any) are removed, even
if you don’t choose to sell the equity at that point in time. The Australian
Tax Office does not consider a lack of action on your part, e.g. choosing not
to sell the asset, as a reason for you to avoid paying tax. This tends to compel
employees at public companies to sell some of their RSUs as they vest, to cover
the taxes on the portion of their RSUs (if any) that they wish to keep.</p>
<p>Summing all of this up gives us the following table:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest or removal of disposal restrictions (whichever is later)</td>
<td>Value of stock on date of vest/removal of restrictions</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Gains on stock since vest/removal of restrictions</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Gains on stock since vest/removal of restrictions</td>
<td>23.5%</td>
</tr>
</tbody>
</table>
<h3 id="-options-in-australia">🍴 Options in Australia</h3>
<p>Things get a bit more strange in Australia when it comes to options. Currently,
the ATO chooses to tax options only once they’ve been exercised – you can
choose to defer paying taxes until that point, when your returns are more
certain, but in doing so you will pay higher taxes (as you’ll be unable to start
the time period of ownership required to claim the capital gains discount until
you’ve paid tax under the ESS provisions).</p>
<p>It’s not possible to exercise an option in Australia until it’s vested, however
if you choose to exercise an option while there are still disposal restrictions
on the underlying share, you’ll continue to defer tax until the point that those
restrictions are lifted.</p>
<p>That yields the following table for options, at present:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise or removal of disposal restrictions (whichever is later)</td>
<td>Value of option*</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Gains on stock since exercise/removal of restrictions</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Gains on stock since exercise/removal of restrictions</td>
<td>23.5%</td>
</tr>
</tbody>
</table>
<p>* We’ll come back to how options are valued by the ATO in the next section</p>
<p>However, this table is only accurate under the current tax regime. Australia’s
tax law for ESS assets changed in July 2015, but does not apply retroactively:
equity granted before July 2015 is still subject to the previous tax law, which
took a different view of how options should be taxed.</p>
<p>The point of contention lies in whether or not failing to exercise an option,
when the underlying share could be sold, is a “lack of action” on your part. As
previously noted, your failure to realize income through inaction is <em>not</em>
considered an acceptable reason by the ATO for you to defer paying tax. Prior
to the 2015 tax change, the ATO did not consider the failure to exercise an
option as a valid reason for that option to have its tax deferred. This meant
that at the point where disposal restrictions on the underlying share were
removed, options became immediately taxable, even if an employee had not
exercised them.</p>
<p>That means that for equity granted prior to July 2015, the following table
applies instead:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest or removal of disposal restrictions (whichever is later)</td>
<td>Value of option*</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Gains on stock since vest/removal of restrictions</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Gains on stock since vest/removal of restrictions</td>
<td>23.5%</td>
</tr>
</tbody>
</table>
<h3 id="-valuing-options">💵 Valuing options</h3>
<p>While Australia has generally tried to keep its equity law simple, the ATO crams
huge amounts of complexity into how it chooses to value options. The most simple
way to value an option is to take the difference between the value of the
underlying share, and the cost to exercise: this is the amount of profit that
could be made were you to exercise the option and immediately sell the resulting
share.</p>
<p>The ATO is unsatisfied with that as a means of valuation though, since it allows
companies to grant equity that is definitionally worthless, but that has
historically proven to have worth in the long run, e.g. by granting options with
an exercise price equal to the current value of the share. To take into account
this likelihood that an option might increase in value in the future, the ATO
has a complicated set of tax tables, which use the percentage discount being
granted and how long the option is valid for, as a way of valuing
potential future gains.</p>
<p>In practice, for a quickly growing tech company this will likely be a moot
point, since the ATO takes the greater of these two valuations, and the spread
between share and exercise prices will outpace the ATO’s calculated value of the
option at a fast-paced company, but this is worth taking into account.</p>
<h3 id="-termination">👋 Termination</h3>
<p>There’s one additional part of the ESS provisions that we haven’t yet mentioned
in our discussions, that of termination. The intent of the ESS provisions is
that they should only apply to current employees of a company, so what should
happen if you choose to leave the company prior to hitting an ESS taxation point
(for example, prior to your disposal restrictions being removed)? The ATO
simply decided that termination of your employment was also an ESS taxing point.
This solves the problem quite neatly for an employer, who no longer needs to be
able to inform past employees if their restrictions change.</p>
<p>That modifies our table for RSUs to look as follows:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Later of vest or removal of disposal restrictions/termination of employment</td>
<td>Value of stock on date of vest/removal of restrictions/termination</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Gains on stock since vest/removal of restrictions/termination</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Gains on stock since vest/removal of restrictions/termination</td>
<td>23.5%</td>
</tr>
</tbody>
</table>
<p>For post-July 2015 options:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Later of exercise or removal of disposal restrictions/termination of employment</td>
<td>Value of stock on date of exercise/removal of restrictions/termination</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Gains on stock since exercise/removal of restrictions/termination</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Gains on stock since exercise/removal of restrictions/termination</td>
<td>23.5%</td>
</tr>
</tbody>
</table>
<p>And for pre-July 2015 options:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Later of vest or removal of disposal restrictions/termination of employment</td>
<td>Value of option*</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Gains on stock since vest/removal of restrictions</td>
<td>47%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Gains on stock since vest/removal of restrictions</td>
<td>23.5%</td>
</tr>
</tbody>
</table>
<p>Notably, this can make it very difficult for employees of private companies to
retain their equity after they leave: even if their company is not yet liquid,
they’ll be required to pay tax on their equity when they leave, and may not be
able to afford the taxes required to do so.</p>
<p>While for most employees this will mean walking away with no equity for their
work, for others, it will mean walking away with no equity <em>and</em> a large tax
bill. For example, if an employee were granted options prior to 2015, with a 90 day
window to exercise when they left – commonplace in the US, where this is
not problematic – then they’ll be obligated to pay tax on those options at
the time of their termination. If they choose not to exercise the options, for
example, because the options are not yet liquid, the ATO will consider that
<em>inaction</em> on the employee’s part, and will not refund the tax when the options
expire 90 days later. A similar event would occur with RSUs, should an employee
choose to leave a company prior to disposal restrictions being lifted, and
without rescinding their equity.</p>
<h3 id="-subjectivity-and-technicalities">🤔 Subjectivity and technicalities</h3>
<p>You might have noticed me describing that the Australian tax system was
“designed” to work in a particular way, rather than saying that it does. This is
because many of the criteria that determine how equity should be taxed are
poorly defined, both in the original law, and in case law that exists.</p>
<p>Take the “real risk of forfeiture”, for example. It’s fairly critical to have a
clear definition for what constitutes a real risk, since it determines the point
at which tax must be paid, which may well occur before the equity is sold. There
is just <a href="https://www.ato.gov.au/law/view/document?docid=%22AID%2FAID201061%2F00001%22">a single piece of case law</a>
that defines what constitutes a real risk, and it is specific to one particular
individual’s situation. It states that a six month vesting cliff for equity,
where tax could not be deferred by more than three years, would constitute a
real risk. It also states that a twelve month cliff for equity, where tax could
be deferred indefinitely, would also constitute a real risk. What happens if you
defer tax indefinitely, but have a cliff that is less than twelve months? The
ATO provides no guidance on this.</p>
<p>The definition of a “genuine disposal restriction” is even more fraught. Understandably,
prior to 2015, companies were eager to argue that they had disposal restrictions,
as it allowed their employees to defer tax for longer. This led the ATO to
impose <a href="https://www.ato.gov.au/general/employee-share-schemes/in-detail/restrictions-and-forfeiture/ess---genuine-disposal-restrictions-and-deferred-taxing-points/#H4">requirements</a> that a disposal restriction was in fact genuine. If there are
caveats in an equity agreement allowing equity to be transferred with board
approval (commonplace in the US), then the board must have a strict policy for
when such a disposal would be approved, which would apply only in rare
circumstances. Even if an equity agreement says that equity cannot be
transferred, but an employee ignores this policy and transfers their equity,
then the employee must be subject to harsh penalties (e.g. termination), or else
the disposal restrction is not considered to exist.</p>
<p>These ambiguities make it difficult for either companies or employees to have
certainty as to how their equity will be taxed. While the July 2015 tax changes
made it easier for companies to opt into particular types of tax treatement (by
specifically referencing certain tax law in their grants), it’s commonplace for
large employers to file case rulings with the ATO to clarify how their equity will be
taxed in Australia. These case rulings are applicable to all employees at that
company, but aren’t much help if you’ve moved internationally while working for
a company too small to have filed such a ruling.</p>
<p>We’ll come back to this problem in the next post, when the rubber hits the road
and we talk about how the combination of two tax systems can lead to a bizarre
set of problems.</p>We’ve talked a little about US equity taxes, but now let’s turn to a different country, and discuss how those same taxes are applied in Australia. If you’re not subject to Australian taxation this might sound irrelevant to you, but we’re going to need two countries to talk about the final details of international tax, so I’d encourage you to stick with it. As ever, I’m not a tax professional: if you spot something that seems off here, I’d love to hear from you.Equity taxes: the US perspective2020-08-31T00:00:00+00:002020-08-31T00:00:00+00:00https://karla.io/2020/08/31/us-taxes<p>In my last post, I talked about how source taxation could mean that you end up
paying US taxes long after you move away from the US, or state taxes within the
US long after you move away from that state. I didn’t talk at all about how
those taxes would be calculated though, and we’ll need to understand that to
talk about the more complex problems that arise when you move. Let’s run through
how the US taxes equity.</p>
<p>First though, we need to understand the structure of the US tax system. For a
host of historical and political reasons, the US levies taxes at three levels:
local, state, and federal. In the Bay Area, local taxes are paid by employers,
so you only need to worry about Californian and US federal taxes.</p>
<p>Within those systems, there are three different types of equity that are
commonly granted by tech companies: Restricted Stock Units (RSUs), Non-Qualified
Stock Options (NSOs), and Incentive Stock Options (ISOs). We’ll talk about each
of these, and their combinations, in turn. Note that it is also possible to be
given Restricted Stock (rather than Restricted Stock <em>Units</em>), but that this is
relatively uncommon in the tech industry, so it isn’t covered here.</p>
<p>As a pretty standard disclaimer, although my partner and I have paid taxes in
the US, we’ve only had to do so for a few years. Everything I say is based on
personal not professional experience. If any of this sounds wrong to you, please
<a href="mailto:hello@karla.io">reach out</a> and I’ll update it!</p>
<h3 id="federal-us-taxes-restricted-stock-units-rsus">🇺🇸Federal US taxes: Restricted Stock Units (RSUs)</h3>
<p>Let’s start with the simple case, and imagine that your company grants you RSUs
as part of your employment package. This means that your company is granting
you stock, typically at no cost, but is doing so in the future: you don’t
realize a tax burden until the RSU vests, and when it does, you’re given
stock in exchange. Typically this happens in a series of tranches, so you’ll see
multiple vests over the life of a grant. When each of these vests happens, the
company is giving you something of value for (usually) no cost, so you pay
regular income taxes on that value. If you later sell that stock, you’ll pay
capital gains tax on the amount that its value has changed since it vested (and
you last paid tax). This will be short term capital gains tax (i.e. income tax)
if you’ve held the stock for less than 12 months, or long-term capital gains tax
(i.e. capital gains tax) if you’ve held it for at least that long.</p>
<p>The US income tax and capital gains tax systems are progressive, with the income
tax brackets being between 10% and 37%, and the capital gains brackets being
between 0% and 20%. For simplicity, we’ll talk only about the highest of these
in our analysis, though your actual tax rates will be lower.</p>
<p>All that yields the following tax statuses for RSUs, federally:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>Income tax</td>
<td>Value of stock at vest</td>
<td>37%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Income tax</td>
<td>Gains on stock since vest</td>
<td>37%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Capital gains tax</td>
<td>Gains on stock since vest</td>
<td>20%</td>
</tr>
</tbody>
</table>
<p>Note that vesting isn’t necessarily just time-bounded, it can be based on a
variety of other factors, as defined in the grant. One common practice for
private tech companies is to grant “double-trigger” RSUs: these have both a
time-based vesting component, and also a further clause, typically around the
availability of liquidity for the stock. You haven’t vested the stock for tax
purposes, and so don’t owe tax, until all conditions of the grant have been met.
(This prevents you from having a tax burden prior to being able to sell the
actual shares, and therefore pay said burden).</p>
<p>In addition to income and capital gains taxes, the US has other federal taxes
that apply to these types of earnings:</p>
<ul>
<li>income is also subject to FICA tax (i.e. Social Security and Medicare taxes), which is a regressive tax of 6.75% diminishing to 1.45%, and then returning to 2.35%. We’ll use 2.35% for our calculations, since the 6.75% ends up being a flat amount above a certain income threshold.</li>
<li>capital gains, both short- and long-term, are also subject to NIIT (Net Investment Income Tax), which is a 3.8% tax on investment gains.</li>
</ul>
<p>That means the <strong>actual</strong> tax status looks more like this:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>Income and FICA taxes</td>
<td>Value of stock at vest</td>
<td>39.35%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Income tax and NIIT</td>
<td>Gains on stock since vest</td>
<td>40.8%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Capital gains tax and NIIT</td>
<td>Gains on stock since vest</td>
<td>23.8%</td>
</tr>
</tbody>
</table>
<p>However, FICA taxes and NIIT are only levied on US tax residents. Further, the
US does not tax capital gains (short-term or long-term) for non-residents, so in
practice only the grant and vesting rows of the first table apply to a US
non-resident who is subject to US source taxation.</p>
<h3 id="californian-taxes-restricted-stock-units-rsus">🐻Californian taxes: Restricted Stock Units (RSUs)</h3>
<p>California takes a fairly similar view of RSUs to the US federally, however
it does not treat long-term capital gains differently to short-term. This
simplifies the tax table, at the expense of paying more taxes on long-term
gains.</p>
<p>Further, while California doesn’t have FICA taxes or NIIT, it instead has an
additional 1% tax created by the Mental Health Services Act. Unlike its federal
equivalents, this tax <em>is</em> levied on Californian non-residents.</p>
<p>California’s income tax brackets for 2020 range from 1% to 12.3%, giving us the
following table for both residents:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>Income and MHSA taxes</td>
<td>Value of stock at vest</td>
<td>13.3%</td>
</tr>
<tr>
<td>Sale</td>
<td>Income and MHSA taxes</td>
<td>Gains on stock since vest</td>
<td>13.3%</td>
</tr>
</tbody>
</table>
<p>California does not tax the sale of stock for non-residents, so the sale row
above isn’t relevant in that situation.</p>
<h3 id="putting-it-all-together-restricted-stock-units-rsus">📚Putting it all together: Restricted Stock Units (RSUs)</h3>
<p>Putting these different tax jurisdictions together yields the following tax
rates on RSUs across the US and California, for a US and Californian resident:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>US Income, US FICA, Cal income, Cal MHSA</td>
<td>Value of stock at vest</td>
<td>52.65%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>US Income, US NIIT, Cal income, Cal MHSA</td>
<td>Gains on stock since vest</td>
<td>54.1%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>US Capital gains, US NIIT, Cal income, Cal MHSA</td>
<td>Gains on stock since vest</td>
<td>37.1%</td>
</tr>
</tbody>
</table>
<p>And for a non-US resident at time of vest and sale:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>US Income, Cal Income, Cal MHSA</td>
<td>Value of stock at vest</td>
<td>50.3%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
</tbody>
</table>
<h3 id="federal-us-taxes-non-qualified-stock-options-nsos">🇺🇸Federal US taxes: Non-qualified Stock Options (NSOs)</h3>
<p>The problem with RSUs for a private company is one of liquidity: you don’t want
your employees having to pay tax for an asset that can’t be sold (because that
creates a liability for them). In order to avoid that, you need to add that
“double-trigger” clause to the vesting schedule, however this means that your
employees will pay income tax on the entirely of their gains once a liquidity
event does occur.</p>
<p>Ideally, you’d like your employees to be able to opt into capital gains tax
status earlier, if they so choose, since that leads to lower tax rates. This is
typically achieved by granting employees options, which are the right to acquire
stock at a fixed price, rather than giving them stock directly, as with an RSU.
Options have a new tax event over RSUs, which occurs when the employee chooses
to exercise their right to acquire stock in exchange for their options.</p>
<p>Generally speaking, tech companies grant their options with no discount
or additional cost, i.e. the fixed price you must pay to acquire stock is
generally the company’s fair market value on the day it is granted (for private
companies this is determined by an independent party, and is called a 409A
value). This means that from a tax perspective, the thing you’re being given is
worthless: you’re being given the right to pay $1 for $1 worth of company. As
such, you’re not taxed at either grant or vest, only when you choose to exercise
that right at a later date, when the value of the underlying stock has changed.
Presumably, you’d only choose to do this if you’d make a profit from the
endeavor, i.e. if the company’s stock value had increased above the price you
needed to pay to exercise it.</p>
<p>For an NSO (distinct from an ISO, which we’ll cover shortly) that leads to the
following tax treatments:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>Income and FICA taxes</td>
<td>Value of stock less cost to exercise</td>
<td>39.35%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>Income tax and NIIT</td>
<td>Gains on stock since exercise</td>
<td>40.8%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>Capital gains tax and NIIT</td>
<td>Gains on stock since exercise</td>
<td>23.8%</td>
</tr>
</tbody>
</table>
<h3 id="californian-taxes-non-qualified-stock-options-nsos">🐻Californian taxes: Non-qualified Stock Options (NSOs)</h3>
<p>Once again, California takes a fairly similar view to the US federally when it
comes to NSOs. Rather than being taxed at vest, they’re taxed at exercise, and
as with everything in California, are subject to income and MHSA taxes.</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>Income and MHSA taxes</td>
<td>Value of stock less cost to exercise</td>
<td>13.3%</td>
</tr>
<tr>
<td>Sale</td>
<td>Income and MHSA taxes</td>
<td>Gains on stock since exercise</td>
<td>13.3%</td>
</tr>
</tbody>
</table>
<h3 id="putting-it-all-together-non-qualified-stock-options-nsos">📚Putting it all together: Non-qualified Stock Options (NSOs)</h3>
<p>Combining the two different tax jurisdictions again, we get the following
table for US and Californian residents at each point:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>US Income, US FICA, Cal Income, Cal MHSA</td>
<td>Value of stock less cost to exercise</td>
<td>52.65%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>US Income, US NIIT, Cal Income, Cal MHSA</td>
<td>Gains on stock since exercise</td>
<td>54.1%</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>US Capital gains, US NIIT, Cal Income, Cal MHSA</td>
<td>Gains on stock since exercise</td>
<td>37.1%</td>
</tr>
</tbody>
</table>
<p>For non-residents, California levies tax at exercise but still not at sale, so
a non-US resident at each of vest, exercise, and sale, would see the following
tax treatment:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>US Income, US FICA, Cal Income, Cal MHSA</td>
<td>Value of stock less cost to exercise</td>
<td>52.65%</td>
</tr>
<tr>
<td>Sale (< 12 months)</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Sale (>= 12 months)</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
</tbody>
</table>
<p>Note that each of these tables are identical to the taxation status of an RSU,
except that an employee is now able to choose when the taxation point will be
hit. They can choose to exercise earlier and have more of their earnings taxed
as capital gains, or they can wait until later when a return is more certain,
and can have their options taxed the same way that they would be as RSUs.</p>
<p>The downsides to NSOs are that they do require capital to exercise: using them
as a form of compensation is more advantageous to well-off employees, and that
they are only valuable to the extent that the value of the company continues to
increase. As companies get larger, they tend to move away from granting NSOs to
instead grant RSUs, since reasoning about the value of RSUs for an offer is
often simpler for candidates.</p>
<h3 id="federal-us-taxes-incentive-stock-options-isos-a-history">🕰Federal US taxes: Incentive Stock Options (ISOs), a history</h3>
<p>ISOs are where things get complicated, so we’ll take a
<a href="https://www.jstor.org/stable/1118677">brief trip into history</a> to understand
what led to this situation in the first place. (If this sounds boring to you,
and you’re familiar with AMT, you can skip to the next section).</p>
<p>Although I’ve described how NSOs are taxed in the US, this definition is based
entirely on common law: there are no statutes describing how NSOs should
actually be taxed (incidentally, this is why NSOs are also known as
“non-statutory stock options”, in addition to “non-qualifying stock options”).</p>
<p>Before ISOs existed, this was problematic: when you granted an option to an
employee, were the gains they realized capital or compensatory in nature, i.e.
were they taxed under the income tax or capital gains tax system? Back in the
1940s, this determination was based on “individual circumstances”, for example
where the original agreement was communicated as compensation, and the relative
value of the cost of exercise to the company’s fair market value at the time of
the grant. This decision was often complicated by common clauses that prevented
an employee from exercising their options after leaving a company, making them
seem more compensatory in nature.</p>
<p>The Revenue Act of 1950 (jokingly known at the time as the “Loophole Closing and
Opening Act”), sought to remedy this situation by creating a special class of
options, defined in statutes, that were guaranteed to be capital. These
“restricted stock options”, which went on to become ISOs, would not be taxed at
exercise, only at sale, and only at capital gains rates. They also had a number
of restrictions attached to them, to prevent them from being used for
non-employees.</p>
<p>When they were first introduced, these restrictions focused on how the options
could be transferred to others, how long they had to be held for before sale,
and how much of a discount could be provided relative to the company’s fair
market value. However, when you create tax loopholes, people use them in
unexpected ways, and so the set of restrictions on ISOs has been added to over
the years. These additions include restrictions on the total value of ISOs an
individual can be granted (added in 1980), and a requirement that options be
granted at no less than the company’s current fair market value (added in 2004).</p>
<p>However, the most impactful change to the tax status of ISOs occurred as a
result of the introduction of the Alternative Minimum Tax, or AMT, in 1982. This
tax was originally created to prevent very high income households from taking so
many deductions that they paid no regular income tax at all. Many of these
households benefited from the loopholes created for ISOs, and so the “bargain
element” of an ISO at the time of exercise was included as income under the AMT
system. This “bargain element” is the difference between the fair market value
of the option at the time of exercise, and the price you had to pay in order to
exercise it (typically the fair market value at the time of grant).</p>
<p>AMT operates in parallel with the regular US income tax system. Any income
counted in the regular income tax system is included in your AMT income, along
with some additional AMT-only income, such as the bargain element of ISOs
that are exercised. The AMT system has a lower maximum tax rate (28%), and a
higher tax-free threshold, but permits very few of the deductions allowed by the
regular income tax system. You calculate your tax burden from the regular
system, and from the AMT system, then pay the higher of the two, in any given
year.</p>
<p>The creation of “AMT-only” income means that the cost basis you have for an
asset (that is, the amount that you are considered to have paid in order to
acquire the asset, and which you’re not taxed on when you realize a gain) can be
different in the two systems. To prevent this from causing double taxation in
the case where you pay AMT one year, but regular income tax in a latter year,
each time you pay AMT you receive a credit. This credit is the amount of
additional tax, above the regular income tax system, that you were required to
pay under AMT. In any subsequent year where you pay under the regular tax system,
you can use your AMT credit to lower the amount of tax you’re required to pay to
the level you would pay under the AMT system in that year.</p>
<p>Capital gains tax exists in the AMT system, and the rates are the same as under
the regular income tax system, but apply to the gains that the AMT system
considers you to have received.</p>
<p>Functionally for ISOs, this means that AMT acts as a sort of prepayment system:
although it causes a higher tax burden at exercise time, you’ll typically get
at least some of that money back as a tax credit on later returns. This breaks
down in a few cases, such as where you don’t pay more tax later. For exmaple,
if the value of your options decreases, does not increase enough, or if you
aren’t required to file later US tax returns.</p>
<h3 id="federal-us-taxes-incentive-stock-options-isos-in-practice">🧮Federal US taxes: Incentive Stock Options (ISOs), in practice</h3>
<p>That’s all a lot to take in, so let’s run through a worked example. Let’s say
you were granted ISOs with a strike price (i.e. cost to exercise) of $1,
exercised them 2 years later when the company’s FMV is $3, and sold the
underlying stock another 2 years later, at a value of $6.</p>
<p>When you exercise, you recognize no income under the regular income tax system
(part of the “incentive” of ISOs), but you do recognize some under AMT.
Your “bargain element” is the company’s fair market value less the price you
paid to exercise, so $2. Assuming that you pay AMT at the highest rates, you’ll
pay 28% tax on this income, which would be $0.56.</p>
<p>Assuming that you exercise enough ISOs in that year, your tax burden under the
AMT system will be higher (since for every option you exercise you add $0.56 of
AMT and $0 of regular tax), and you’ll pay under the AMT system, rather than
the regular tax system, that year.</p>
<p>When you sell, you recognize capital gains under the regular income tax system,
rather than income (the other part of the “incentive” of ISOs). These gains are
from the sale price down to the original cost to exercise, i.e. $5. Since you’ve
held the options for more than two years, these are long-term capital gains, so
you’d pay at most 20% on them, or $1 in tax.</p>
<p>You also recognize capital gains under the AMT tax system at sale, however the
gains are only on the difference from the sale price to the fair market value at
time of exercise (since under AMT you’ve paid tax on the rest of the asset
already). This means you have $3 of gain under the AMT system, at a 20% rate,
yielding $0.60 more tax.</p>
<p>In the year of sale, assuming you sell enough shares that were originally ISOs,
your tax burden under the regular system will be higher (for every share sold,
you add $1 of regular income tax, but only $0.60 of AMT). However, you’ll have
an AMT credit available from the year that you exercised. That year, you paid an
additional $0.56 over the regular income tax system by paying under the AMT
system, which means you can claim a credit of up to that much back on this
return. However, you can only reduce your tax burden down to the amount of AMT
that you’re required to pay, i.e. a reduction of $0.40 to $0.60 in the year of
sale. This means that you’d use up $0.40 of your credit, but would have another
$0.16 available for a future tax year in which your regular income tax was
higher.</p>
<p>In the end, you’ll have paid $0.56 + $0.60 = $1.16 of tax on a gain of $5,
giving a tax rate of 23.2%. Had this instead been an NSO, the tax rate would
have been 26.8%. If instead the company’s value had increased further by the
time you had sold, e.g. to $10, you’d be able to claim back the entire AMT
credit you’d paid at exercise, and would have a tax rate of exactly 20%.</p>
<p>Note that in addition to each of these taxes, you’ll still be required to pay
Net Investment Income Tax at the time of sale on your total gain, adding an
additional 3.8% tax.</p>
<h3 id="federal-us-taxes-incentive-stock-options-isos-summarizing">🇺🇸Federal US taxes: Incentive Stock Options (ISOs), summarizing</h3>
<p>To wrap up our discussion on ISOs, we also need to mention what happens if you
choose to violate the restrictions required for their preferential treatment.
For example, if you sell an ISO within 12 months of exercising it, or 2 years of
its grant. In this case, a “disqualifying disposition” (aka disqualifying sale)
has occurred, your ISO reverts to NSO status, and you lose the tax advantage it
would typically have given you.</p>
<p>Taking all of this into account gives us the following US federal tax table for
qualifying dispositions of ISOs:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>AMT</td>
<td>FMV at exercise less cost to exercise</td>
<td>28%</td>
</tr>
<tr>
<td>Sale (if AMT is higher)</td>
<td>Capital gains tax and NIIT</td>
<td>Value of stock less FMV at exercise (CGT), value of stock less cost to exercise (NIIT)</td>
<td>20% + 3.8%</td>
</tr>
<tr>
<td>Sale (if regular tax is higher)</td>
<td>Capital gains tax and NIIT</td>
<td>Value of stock less cost to exercise (CGT and NIIT), less AMT previously paid, down to the AMT amount in the row above</td>
<td>23.8% (full gains), less prior AMT</td>
</tr>
</tbody>
</table>
<p>As before, NIIT isn’t levied on non-US tax residents, nor are capital gains
taxes, so a non-US tax resident can ignore the two Sale rows.</p>
<h3 id="californian-taxes-incentive-stock-options-isos">🐻Californian taxes: Incentive Stock Options (ISOs)</h3>
<p>California has the same requirements for ISOs as the US federally, so the tax
table looks functionally the same, with different numbers. The AMT rate in
California is 7% and AMT credits are also available.</p>
<p>This gives the following table for qualifying dispositions of ISOs:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>AMT</td>
<td>FMV at exercise less cost to exercise</td>
<td>7%</td>
</tr>
<tr>
<td>Sale (AMT higher)</td>
<td>Income and MHSA</td>
<td>Value of stock less FMV at exercise</td>
<td>13.3%</td>
</tr>
<tr>
<td>Sale (Regular tax higher)</td>
<td>Income and MHSA</td>
<td>Value of stock less cost to exercise (Income and MHSA), less AMT previously paid, down to the AMT amount in the row above</td>
<td>13.3% (full gains), less prior AMT</td>
</tr>
</tbody>
</table>
<h3 id="putting-it-all-together-incentive-stock-options-isos">📚Putting it all together: Incentive Stock Options (ISOs)</h3>
<p>And combining both of these tax tables gives the following overall status for
qualifying ISO sales:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>US AMT, Cal AMT</td>
<td>FMV at exercise less cost to exercise</td>
<td>35%</td>
</tr>
<tr>
<td>Sale (if AMT is higher)</td>
<td>US Capital gains, US NIIT, Cal Income, Cal MHSA</td>
<td>Value of stock less FMV at exercise (CGT, Cal income tax, MHSA), value of stock less cost to exercise (NIIT)</td>
<td>33.3% + 3.8%</td>
</tr>
<tr>
<td>Sale (if regular tax is higher)</td>
<td>US Capital gains, US NIIT, Cal Income, Cal MHSA</td>
<td>Value of stock less cost to exercise (CGT, NIIT, Cal income tax, MHSA), less AMT previously paid, down to the AMT amount in the row above</td>
<td>37.1% (full gains), less prior AMT</td>
</tr>
</tbody>
</table>
<p>For non-residents, we’d instead get this table:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Tax type</th>
<th>Taxed amount</th>
<th>Highest tax rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>Grant</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Vest</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>Exercise</td>
<td>US AMT, Cal AMT</td>
<td>FMV at exercise less cost to exercise</td>
<td>35%</td>
</tr>
<tr>
<td>Sale</td>
<td>None</td>
<td>N/A</td>
<td>N/A</td>
</tr>
</tbody>
</table>
<p>It’s worth noting that this means that non-residents exclusively pay AMT on
their ISOs when exercising internationally, and are unlikely to have sufficient
regular income to receive this back as a credit in the future.</p>
<h3 id="wrapping-up">🎁Wrapping up</h3>
<p>As has hopefully become clear, a big complication in the US tax system as it
pertains to equity is the existence of ISOs and AMT. While there are a confusing
series of taxes that apply to each class of equity, the existence of an
intentionally tax preferential type of option, combined with two parallel tax
systems, makes it quite difficult to reason about just how much tax you’ll end
up paying.</p>
<p>One other, final factor in the US tax system is that it is a joint assessment
system: if you are married (or in a similar relationship recognized by the
assessment authorities), you’re generally expected to file a single tax return
each year declaring your combined incomes. Further, California is a community
property state, which means that assets earned while married in California are
considered to belong to the relationship, rather than each individual. This
community property status makes little to no difference when filing joint
returns, but can introduce problems when you later move to a separate property
state (e.g. another country or 43 of the other US states).</p>
<p>Before discussing that issue though, we’ll need to understand another country’s
tax system, so that we can see where the sharp edges between the different
systems come into play.</p>
<p>In the next post, I’ll cover Australian equity taxation, and then we’ll put
everything we’ve learned back together to see the sorts of things that go wrong
when filing international taxes with equity.</p>In my last post, I talked about how source taxation could mean that you end up paying US taxes long after you move away from the US, or state taxes within the US long after you move away from that state. I didn’t talk at all about how those taxes would be calculated though, and we’ll need to understand that to talk about the more complex problems that arise when you move. Let’s run through how the US taxes equity.Equity taxes: What happens when you move?2020-07-16T00:00:00+00:002020-07-16T00:00:00+00:00https://karla.io/2020/07/16/source-taxation<p>Let’s talk tax! Taxes aren’t the most interesting of topics, but as a mass
exodus from San Francisco begins in the wake of the COVID-19 pandemic, there are
a whole bunch of people working at Silicon Valley tech companies who are going
to have to start dealing with them in much more depth.</p>
<p>In general, people expect that taxes will be complicated in the year that they
move. However, if you’re being granted equity, as most Silicon Valley employees
are, you might be surprised to discover that they will be complicated for much
longer.</p>
<p>If you choose to move internationally, rather than just interstate, this counts
for double. My partner and I — neither of us US citizens — moved from San
Francisco to Sydney three years ago, and have spent the last few years figuring
out our tax situation. We struggled a lot to find good guidance on any of this
online, so I’m going to take the time to document what we learned in a few
posts, so that I can make the path more well-trodden for the next person.</p>
<p>Although I’ve spent a lot of time learning about this, I’m not a tax
professional. If any of it sounds wrong to you, or if you have your own story to
share, please <a href="mailto:hello@karla.io">reach out</a>!</p>
<h3 id="trailing-tax-liabilities">📆Trailing tax liabilities</h3>
<p>Income taxes sound like they should be simple: at the end of a tax year, you pay
a percentage of the amount that you earned that year to the relevant tax agency.
In some countries, your employer collects these taxes as you earn them
(<a href="https://en.wikipedia.org/wiki/Pay-as-you-earn_tax">PAYE/PAYG</a>), and you need
to think about them even less.</p>
<p>Equity complicates this situation. When you’re granted equity, you’re being
promised something of value. There’s a problem though: that equity might not yet
be liquid — it could have a vesting schedule, you might not have exercised it,
it could be in a private company… and if it’s not liquid, you probably
shouldn’t have to pay anything when it’s given to you. Many tax jurisdictions
handle this by choosing to tax you at a later point: rather than when the equity
is granted, instead taxing you when it vests, is exercised (in the case of
options), or is sold.</p>
<p>However, imagine you move tax jurisdictions between the date where you’re given
the equity, and the date on which you actually pay tax. The first jurisdiction
now has a problem: in their eyes, you earned that equity while you lived there,
and they only gave you a free pass on immediately paying tax on it through the
goodness of their hearts. When you eventually pay tax in the second tax
jurisdiction, they want a piece of that too.</p>
<p>This is a trailing tax liability.</p>
<p>Broadly speaking, any time that you’re granted equity in one jurisdiction, but
only trigger a taxation event on it in another, the first jurisdiction is likely
to say that they also have taxation rights.</p>
<p>That first jurisdiction is said to have “source” taxation rights, because that’s
where the asset was sourced, while the second is said to have “residency”
taxation rights, i.e. the right to tax you because you are presently a resident.</p>
<h3 id="apportioning-income">💰Apportioning income</h3>
<p>Now presumably, if you worked in both jurisdictions between the point where you
were given the equity, and the point at which you pay tax, there’s some
percentage that each should be considered the source of.</p>
<p>Internationally, this apportionment is governed by pairwise tax treaties between
countries. These are typically based on the
<a href="https://read.oecd-ilibrary.org/taxation/model-tax-convention-on-income-and-on-capital-condensed-version-2017_mtc_cond-2017-en#page28">OECD model tax convention</a>,
which helpfully has an in-depth commentary on this exact point
(<a href="https://read.oecd-ilibrary.org/taxation/model-tax-convention-on-income-and-on-capital-condensed-version-2017_mtc_cond-2017-en#page329">Commentary on Article 15, 12.14</a>).
In short, unless otherwise agreed between two countries, income is allocated
based on the percentage of <em>workdays</em> spent in each jurisdiction, from the day
the equity was granted, to the day that it vested. Note that this percentage
will differ for each tranche, or part of a grant, that vests on a different day.</p>
<p>However, those treaties only apply at the country to country level, i.e. for
taxes levied federally. In addition to imposing federal taxes, the US also has
state taxes, and these are not governed by international tax treaties.</p>
<p>That allows states to take a different view of how to decide whether income
belongs to them, and in fact, they do. For restricted stock, California takes
the same view as the tax treaty, and considers the period from grant to
vest. However, for someone granted options in California, who is a non-resident
when they exercise those options, the source is calculated by the percentage of
workdays spent in California from grant to <em>exercise</em>, rather than to vest (per
the <a href="https://www.ftb.ca.gov/forms/misc/1004.html">Equity-Based Compensation Guidelines</a>).</p>
<h3 id="residency-taxation">🏠Residency taxation</h3>
<p>But the tax doesn’t stop there! In addition to the source jurisdiction taxing
you for the percentage they believe is theirs, the jurisdiction in which you are
currently a resident will also want to tax you, typically on 100% of the
income you earn from an asset. This can lead to double taxation: residency
taxation means you need to pay tax on 100% of the income to your current tax
jurisdiction, but you also need to pay tax on some percentage of the income
(potentially up to 100% again) to the source jurisdiction.</p>
<p>Many tax agencies provide relief against this situation, in the form of tax
credits: if you’ve already had to pay tax in one location for the same event,
they won’t charge you again.</p>
<h3 id="worked-example">📔Worked example</h3>
<p>That’s a lot to take in, so let’s work through an example:</p>
<p>Let’s assume you were granted 1,000 non-qualified stock options (NSOs) with a
strike price of $1.00, on 2018-07-01, while a tax resident of California.
The options vest once a year for the next four years, in four equal tranches of
250.</p>
<p>(If you’re not familiar with options, an NSO gives you the right to purchase a
given company’s stock at a given price, regardless of its current market value.
In this case, the option would be valuable if the company’s stock price rose
above $1.00, because you could exercise your right to pay the company $1.00 for
a share, and then immediately sell that share at its market value, taking the
difference as a profit, and consequently, as income.)</p>
<p>About a year and a half later, on 2020-03-01, you move from California to
Melbourne, Australia. We can figure out the source of each of those tranches
like so:</p>
<table>
<thead>
<tr>
<th>Tranche</th>
<th>Relevant work period</th>
<th>Total workdays</th>
<th>US workdays</th>
<th>AU workdays</th>
<th>US source %</th>
<th>AU source %</th>
</tr>
</thead>
<tbody>
<tr>
<td>2019-07-01</td>
<td>2018-07-01 – 2019-07-01</td>
<td>105</td>
<td>105</td>
<td>0</td>
<td>100%</td>
<td>0%</td>
</tr>
<tr>
<td>2020-07-01</td>
<td>2018-07-01 – 2020-07-01</td>
<td>209</td>
<td>174</td>
<td>35</td>
<td>83%</td>
<td>17%</td>
</tr>
<tr>
<td>2021-07-01</td>
<td>2018-07-01 – 2021-07-01</td>
<td>313</td>
<td>174</td>
<td>139</td>
<td>56%</td>
<td>44%</td>
</tr>
<tr>
<td>2022-07-01</td>
<td>2018-07-01 – 2022-07-01</td>
<td>417</td>
<td>174</td>
<td>243</td>
<td>42%</td>
<td>58%</td>
</tr>
</tbody>
</table>
<p>Internationally, these percentages apply regardless of when you choose to
exercise those options, provided you do so after the date you move to Australia.
However, those options are also subject to taxation within California, and as
we’ve mentioned, the tax agency there believes in a different source percentage
scheme.</p>
<p>Let’s say that you don’t touch that equity until three years after your move,
on 2023-03-01, when you choose to exercise it all.</p>
<table>
<thead>
<tr>
<th>Tranche</th>
<th>Relevant work period</th>
<th>Total workdays</th>
<th>California workdays</th>
<th>California source %</th>
</tr>
</thead>
<tbody>
<tr>
<td>2019-07-01</td>
<td>2018-07-01 – 2023-03-01</td>
<td>487</td>
<td>174</td>
<td>36%</td>
</tr>
<tr>
<td>2020-07-01</td>
<td>2018-07-01 – 2023-03-01</td>
<td>487</td>
<td>174</td>
<td>36%</td>
</tr>
<tr>
<td>2021-07-01</td>
<td>2018-07-01 – 2023-03-01</td>
<td>487</td>
<td>174</td>
<td>36%</td>
</tr>
<tr>
<td>2022-07-01</td>
<td>2018-07-01 – 2023-03-01</td>
<td>487</td>
<td>174</td>
<td>36%</td>
</tr>
</tbody>
</table>
<p>If we assume that once exercised, but not prior, your options become liquid
shares, you’ll be taxed by each of the US federally, California, and Australia
on the day you exercise. Taking just the 2020-07-01 tranche into consideration:</p>
<ul>
<li>you’ll be taxed on 83% of the income in the US,</li>
<li>you’ll be taxed on 36% of the income in California, and</li>
<li>you’ll be taxed on 100% of the income in Australia, which has residency
taxation rights. However, you’ll be able to get tax credits for tax paid
in the US federally on the 83% of the income considered to be foreign-sourced.</li>
</ul>
<p>This means that despite having lived outside of the US and California for 3
years on the date of exercise, you’ll still need to declare US and Californian
income, and pay tax in those jurisdictions. If you’d moved interstate within the
US, you’d still be filing federal returns regardless, but you’d also be required
to file a Californian return that year.</p>
<h3 id="gotchas">🔎Gotchas</h3>
<p>Note that there’s a big gotcha in the worked example above: you only got to
claim tax credits in Australia for the 83% income that the US taxed federally,
but not for the 36% that California taxed.</p>
<p>That’s because, as previously mentioned, the tax treaties only operate between
pairs of countries, not between one country and a state in the other. This
means it’s quite possible that you won’t be able to get tax credits for taxes
paid to a state of another country, and will end up double taxed on part of your
income.</p>
<p>For the worked example, Australia specifically
<a href="https://www.ato.gov.au/law/view/pdf/pbr/it2507.pdf">does consider Californian taxes to be a part of the US income tax system</a>,
so you would in actuality be allowed to claim credits for taxes paid there,
though the same isn’t true in all other countries.</p>
<h3 id="investments">📈Investments</h3>
<p>You might wonder what happens when you eventually sell the shares you get as
a result of exercising – will those also be subject to US or Californian
taxes? While I don’t have personal experience with this situation, the OECD
tax treaty commentary clarifies this point in
(<a href="https://read.oecd-ilibrary.org/taxation/model-tax-convention-on-income-and-on-capital-condensed-version-2017_mtc_cond-2017-en#page326">Article 15, 12.2</a>):
only the income effectively connected to your employment is taxable in the
source jurisdiction. Since exercising is considered to be an investment
decision, any increase in value of the share resulting from the exercise of an
option is considered to be a capital gain, and is therefore <em>not</em> taxable by the
source jurisdiction. Helpfully, this is also California’s interpretation, giving
us some much-needed consistency between what happens internationally and within
California.</p>
<p>If instead you had chosen to exercise prior to moving, the OECD commentary
states that when you moved the asset would be treated as being capital in
nature, and so would only be taxable in your jurisdiction of residence at sale.
However, several countries, including the US and Australia, impose capital gains
taxes on citizens and permanent residents who move their residency to be in
another country.</p>
<h3 id="️-different-taxing-points">➡️ Different taxing points</h3>
<p>Although it might seem complex, in our example above, it was fairly simple to
reason about how tax credits would be applied, because the taxation events
occurred at the same time in each of the different jurisdictions. However, this
is far from a guarantee — different tax agencies can have different views
of the point at which taxation should occur.</p>
<p>This complicates the discussion, because we end up needing to have three
different views of the world: what happened according to each of the source
jurisdiction, the residency jurisdiction, and the tax treaty.</p>
<p>These can get out of sync with one another! In order to understand how that
happens, and talk about how to reconcile the inconsistencies, we’ll need a
better understanding of how equity is taxed domestically in each jurisdiction.</p>
<p>In the next post, I’ll start with a deep dive into US equity taxation, then
move onto Australian equity taxation (less broadly relevant, but we need a
second country and it’s the one I’m most familiar with), then we’ll put what
we’ve learned back together to see how many edge cases we can find.</p>Let’s talk tax! Taxes aren’t the most interesting of topics, but as a mass exodus from San Francisco begins in the wake of the COVID-19 pandemic, there are a whole bunch of people working at Silicon Valley tech companies who are going to have to start dealing with them in much more depth.Ichthyology: Phishing as a Science2018-03-04T00:00:00+00:002018-03-04T00:00:00+00:00https://karla.io/2018/03/04/ichthyology<p>I gave a talk about phishing at a few different conferences last year, and
people occasionally ask me for the whitepaper and / or recording. They’re not
very discoverable at the moment, so I figure I’ll link them here, and then I’ll
have a better answer than “search my browser history”.</p>
<p>The most important takeaways from the talk are that we are all vulnerable to
phishing, and that phishing training usually doesn’t help, but that with a small
amount of inconvenience you can prevent yourself from being phished.</p>
<p>If you’re interested in learning more, there’s a <a href="https://www.youtube.com/watch?v=Z20XNp-luNA">Youtube recording of the
talk</a>, or a <a href="https://karla.io/files/ichthyology-wp.pdf">whitepaper</a>,
or <a href="https://karla.io/files/ichthyology-slides.pdf">slides</a>.</p>
<p>There’s also been some <a href="https://www.offensivecon.org/speakers/2018/markus-and-michele.html">interesting</a>
<a href="https://www.wired.com/story/chrome-yubikey-phishing-webusb/">research</a> done
since my talk by Markus Vervier and Michele Orrù. This research focuses on how
to bypass U2F browser protections and make your own FIDO requests to a security
key from an untrusted domain. This isn’t due to a flaw in the FIDO protocol
itself, but rather is an unexpected side effect of the new WebUSB protocol, and
shows the constant tension between features and security.</p>I gave a talk about phishing at a few different conferences last year, and people occasionally ask me for the whitepaper and / or recording. They’re not very discoverable at the moment, so I figure I’ll link them here, and then I’ll have a better answer than “search my browser history”.Don’t Panic: there’s more TTL2016-06-13T00:00:00+00:002016-06-13T00:00:00+00:00https://karla.io/2016/06/13/dont-panic<p>A bored network engineer caused a bit of a stir on the Internet in early 2013: they set up an IP
that when <code class="language-plaintext highlighter-rouge">traceroute</code>‘d returned the introductory text of Star Wars: Episode IV. Their
<a href="http://beaglenetworks.net/post/42707829171/star-wars-traceroute">original implementation</a> bounced
packets between two different routers, with a series of virtual routing tables. I don’t have access
to routers that are that configurable, but I thought this was super cool so I set out to make my own
version with just a single server in the cloud.</p>
<p>As a brief recap, <code class="language-plaintext highlighter-rouge">traceroute</code> (or <code class="language-plaintext highlighter-rouge">tracert</code> if you’re on Windows) is a built-in utility designed to
show you the path your packets are taking on their way to another server. For example, if you
run <a href="http://network-tools.com/default.asp?prog=trace&host=google.com"><code class="language-plaintext highlighter-rouge">traceroute google.com</code></a> you
can see each IP your packets hop between before reaching a Google server, as well as how long it
took to reach each of them. Day to day, I mostly use it to debug why I can’t access Australian websites:
is our ISP dropping packets, or are the trans-Pacific Internet cables having a bad day?</p>
<p>However, with some trickery I managed to get <code class="language-plaintext highlighter-rouge">traceroute panic.karla.io</code> to return some surprising
results (I’ve since taken this down, because IPv4 addresses are expensive!):</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="code"><pre>107.170.239.254 (107.170.239.254) 0.726 ms 0.641 ms 0.632 ms
198.199.99.233 (198.199.99.233) 0.517 ms 0.509 ms 0.401 ms
xe-0-4-0-17.r06.plalca01.us.bb.gin.ntt.net (129.250.203.81) 1.315 ms 1.436 ms 1.432 ms
ae-1.amazon.plalca01.us.bb.gin.ntt.net (140.174.21.182) 2.703 ms 39.828 ms 2.617 ms
* * *
* * *
72.21.222.19 (72.21.222.19) 2.804 ms 2.731 ms 2.752 ms
0--------------------------------------------0 (45.32.128.93) 2.959 ms 3.393 ms 3.626 ms
All.you.really.need.to.know.for.the.moment (45.32.131.117) 3.742 ms 3.775 ms 3.895 ms
is.that.the.universe.is.a.lot.more.complicated (108.61.215.165) 4.018 ms 2.854 ms 2.894 ms
than.you.might.think (45.32.215.19) 2.989 ms 3.264 ms 3.004 ms
Even.if.you.start.from.a.position.of.thinking (45.32.222.141) 2.548 ms 2.980 ms 2.591 ms
its.pretty.damn.complicated.in.the.first.place (45.32.215.2) 2.936 ms 3.251 ms 2.801 ms
Douglas.Adams (45.32.213.202) 2.884 ms 3.062 ms 3.039 ms
Mostly.Harmless (108.61.215.134) 3.157 ms 2.831 ms 3.492 ms
0--------------------------------------------0 (108.61.192.189) 3.366 ms 3.147 ms 3.090 ms
more.at.karla.io (52.9.216.11) 2.978 ms 3.107 ms 2.774 ms
</pre></td></tr></tbody></table></code></pre></figure>
<p>Getting this to work from just one server turned out to be quite a bit more involved than I
expected!</p>
<h1 id="traceroute-basics"><code class="language-plaintext highlighter-rouge">traceroute</code> basics</h1>
<p>I already had a reasonable understanding of how <code class="language-plaintext highlighter-rouge">traceroute</code> works – it sends a series of
packets with incrementing <a href="https://en.wikipedia.org/wiki/Time_to_live">Time to live</a> fields, so that
each hop along the way has to send a “TTL expired” message. Then, it uses the source IP of each of
those expiry messages to show you the path your packets are taking, showing the reverse DNS lookup
of each server in addition to its IP.</p>
<p>For example, if you were to <code class="language-plaintext highlighter-rouge">traceroute</code> a local media server from your laptop, you would initially
send a packet destined to the media server with a TTL of 1. That packet would make it one hop, to
your router, which would decrement the TTL to 0 and return a failed delivery message with the error
“TTL expired”.</p>
<p><img src="/images/posts/ttl_request1.png" width="100%" style="float: none; padding: 0" alt="Request with TTL of 1" /></p>
<p>You would then increment the TTL field of the next packet to send, making it 2. That packet would
make it to the router, which would decrement the TTL to 1, and then make it one hop further, to the
media server. Since the media server is the packet’s final destination, it would send back an echo
response, rather than an error, and <code class="language-plaintext highlighter-rouge">traceroute</code> would stop sending packets.</p>
<p><img src="/images/posts/ttl_request2.png" width="100%" style="float: none; padding: 0" alt="Request with TTL of 2" /></p>
<p>In practice, nearly all destinations are more than 2 hops from you on the Internet, but the
underlying principle is the same.</p>
<p>The thing I wasn’t sure of with <code class="language-plaintext highlighter-rouge">traceroute</code> was which protocol it was using for all of this.
Clearly IP was being used for the network layer, but I didn’t know which protocol was being used
inside that. A quick look at the <a href="https://en.wikipedia.org/wiki/Traceroute">Wikipedia page</a> showed
that this is actually not defined! The de facto standard is that Windows uses ICMP packets, while
Linux uses UDP packets (though you ask the latter to use ICMP with <code class="language-plaintext highlighter-rouge">traceroute -I</code>). In practice, it
makes little difference which packet type you use – TTL expired messages are always over ICMP,
so the protocol choice only changes the success message that’s sent by the destination server. If
you use ICMP <code class="language-plaintext highlighter-rouge">traceroute</code>, you’ll get back an ICMP “echo response”, while UDP <code class="language-plaintext highlighter-rouge">traceroute</code> will get
you an ICMP “destination port unreachable” message.</p>
<h1 id="lying-to-traceroute">Lying to <code class="language-plaintext highlighter-rouge">traceroute</code></h1>
<p>Now the fun part, figuring out how to make <code class="language-plaintext highlighter-rouge">traceroute</code> do what I wanted instead! Since I would be
running this from just the one server, I’d need to do a fair amount of lying:</p>
<ul>
<li>my server would need to return multiple different results to <code class="language-plaintext highlighter-rouge">traceroute</code>,</li>
<li>each of those results would need to have a different IP, and</li>
<li>each of those IPs would need to have reverse DNS entries without a forward pointing (normal) DNS
record.</li>
</ul>
<p>My plan for returning multiple results was to use the TTLs of the original packets received to
determine the response I should be sending. I’d need to spoof the source IPs for each of those
responses to get the different IPs, and would need to find some way to set up reverse DNS for each
of them.</p>
<h1 id="networking-in-python">Networking in Python</h1>
<p>I decided my first goal would be to get <code class="language-plaintext highlighter-rouge">traceroute</code> to output the same line multiple times. That
meant returning TTL expired packets from the destination host a few times, before returning a
success packet – it was time to start writing Python networking code!</p>
<p>I also decided to start by handling ICMP <code class="language-plaintext highlighter-rouge">traceroute</code>, since then all of the messages I’d need to
send and receive would be the same protocol. That meant my first step was to figure out how to get
Python to listen for ICMP packets, rather than the usual TCP or UDP packets. Some Googling led me to
the <code class="language-plaintext highlighter-rouge">socket.IPPROTO_ICMP</code> option with the <code class="language-plaintext highlighter-rouge">socket.SOCK_RAW</code> type, giving me the following code that
let me see ICMP echo requests (i.e. ping requests) being made to my server:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">socket</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_RAW</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">IPPROTO_ICMP</span><span class="p">)</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">data</span><span class="p">,</span> <span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span> <span class="o">=</span> <span class="n">sock</span><span class="p">.</span><span class="n">recvfrom</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
<span class="k">print</span> <span class="nb">repr</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></code></pre></figure>
<p>Helpfully, the <code class="language-plaintext highlighter-rouge">socket.SOCK_RAW</code> option meant I’d see the entire original IP packet, so it was easy
to extract the TTL of the packet that was sent. Unhelpfully, it also meant I’d need to parse the
contents of the packet myself. I searched for an existing Python library that did this, but found
nothing other pcap parsing libraries, so I settled down and wrote a very basic parser and packet
generator of my own.</p>
<p>Once I could successfully parse messages, it was time to start replying to them! Linux really wants
to help you out when it comes to ICMP, and the kernel will automatically respond to ping requests
on your behalf unless you tell it otherwise. Linux has a convenient <code class="language-plaintext highlighter-rouge">sysctl</code> that you can use to
control this behavior: setting <code class="language-plaintext highlighter-rouge">/proc/sys/net/ipv4/icmp_echo_ignore_all</code> to <code class="language-plaintext highlighter-rouge">1</code> disables ICMP
replies.</p>
<p>With this option turned on, <code class="language-plaintext highlighter-rouge">ping</code> stopped working completely on my server and I was in control. A
small bit of TTL checking logic later, and I was able to repeat the final line of <code class="language-plaintext highlighter-rouge">traceroute</code> as
many times as I wanted. Part one of three complete!</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">socket</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_RAW</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">IPPROTO_ICMP</span><span class="p">)</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">data</span><span class="p">,</span> <span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span> <span class="o">=</span> <span class="n">sock</span><span class="p">.</span><span class="n">recvfrom</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
<span class="n">ip_packet</span> <span class="o">=</span> <span class="n">parse_ip_packet</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">ip_packet</span><span class="p">.</span><span class="n">ttl</span> <span class="o"><</span> <span class="mi">4</span><span class="p">:</span>
<span class="n">send_ttl_expired</span><span class="p">(</span><span class="n">addr</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">send_echo_response</span><span class="p">(</span><span class="n">addr</span><span class="p">)</span></code></pre></figure>
<h1 id="nat-nat-nat">NAT NAT NAT</h1>
<p>Next up was controlling not only the number of lines output by <code class="language-plaintext highlighter-rouge">traceroute</code>, but also the IPs on
those lines. That meant faking the source IP of the “TTL expired” packets I was sending.</p>
<p>The source IP of the packet is stored in the IP header, so I turned on the <code class="language-plaintext highlighter-rouge">socket.IP_HDRINCL</code>
socket option to tell the kernel that we’d be adding the IP headers ourselves, and that it shouldn’t
do so on our behalf. Some tweaking later, and… it didn’t work. I fiddled with the code a
bunch, and still no luck – <code class="language-plaintext highlighter-rouge">tcpdump</code> would show packets leaving the server, but they’d never
make it back to my laptop.</p>
<p>A bunch of investigation and time spent staring at <a href="https://en.wikipedia.org/wiki/Pcap">pcaps</a>
later, I decided that the issue had to be <a href="https://en.wikipedia.org/wiki/Network_address_translation">NAT</a>.
NAT dynamically rewrites packets as they flow through your network, and is commonly used in both
home networking and in data centers, to give the appearance of having a globally unique IP when that
isn’t actually the case.</p>
<p>Comparing the packets leaving my machine to those arriving on the server, it was clear that they
were being rewritten along the way. Since the packets were being rewritten on their way into the
server, they’d also need to be rewritten on the way out. Unfortunately, this made spoofing the
source IP impossible, since when a spoofed packet arrived at the router, the router had no way to
know who sent the packet, and would drop it.</p>
<p>This ruled Digital Ocean out of the hosting providers I could use. I tested a number of others, and
eventually figured out that AWS would let me spoof source IPs if I turned off the
“Source/Dest Check”. With interim routers no longer a problem, I was up and spoofing IPs –
nearly there!</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">socket</span>
<span class="n">IPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="s">'123.1.1.1'</span><span class="p">,</span>
<span class="s">'123.1.1.2'</span><span class="p">,</span>
<span class="s">'123.1.1.3'</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_RAW</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">IPPROTO_ICMP</span><span class="p">)</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">data</span><span class="p">,</span> <span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span> <span class="o">=</span> <span class="n">sock</span><span class="p">.</span><span class="n">recvfrom</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
<span class="n">ip_packet</span> <span class="o">=</span> <span class="n">parse_ip_packet</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">ip_packet</span><span class="p">.</span><span class="n">ttl</span> <span class="o"><=</span> <span class="nb">len</span><span class="p">(</span><span class="n">IPS</span><span class="p">):</span>
<span class="n">send_ttl_expired</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">IPS</span><span class="p">[</span><span class="n">ip_packet</span><span class="p">.</span><span class="n">ttl</span> <span class="o">-</span> <span class="mi">1</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">send_echo_response</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">ip_packet</span><span class="p">.</span><span class="n">dest_ip</span><span class="p">)</span></code></pre></figure>
<h1 id="reverse-dns">Reverse DNS</h1>
<p>The last thing I needed to get set up for my ICMP proof of concept was the reverse DNS records.
These would change the text that was being shown by <code class="language-plaintext highlighter-rouge">traceroute</code>, and complete the trick!</p>
<p>Deceptively, per <a href="https://tools.ietf.org/html/rfc1101#section-4">the RFC</a>, reverse DNS records
aren’t required to match the normal DNS records for the same IPs. That means that <code class="language-plaintext highlighter-rouge">example.com</code> can
point to <code class="language-plaintext highlighter-rouge">1.2.3.4</code>, while <code class="language-plaintext highlighter-rouge">1.2.3.4</code>’s reverse DNS lookup can instead point to <code class="language-plaintext highlighter-rouge">other-example.com</code>.
In fact, a reverse DNS lookup is just a special type of normal DNS lookup.</p>
<p>Say you wanted to lookup the reverse DNS for the IP 52.9.216.11, which you could do by running
<code class="language-plaintext highlighter-rouge">nslookup 52.9.216.11</code>. In reality, this is doing a DNS lookup on the domain
<code class="language-plaintext highlighter-rouge">11.216.9.52.in-addr.arpa</code>, then fetching the PTR record there. The <code class="language-plaintext highlighter-rouge">in-addr.arpa</code> host is a special
domain used for reverse DNS lookups, while the first part of the hostname is the IP address
backwards. You can try this yourself by running <code class="language-plaintext highlighter-rouge">dig -t PTR 11.216.9.52.in-addr.arpa</code>!</p>
<p>Unfortunately, while nothing in the protocol enforces that a forward pointing DNS entry must exist
in order to set up a reverse DNS entry, a number of providers do have this requirement, including
AWS. That meant I could use them for the final line of my output, but none of the rest, so I began
my search for another IP address provider.</p>
<p>Due to <a href="https://en.wikipedia.org/wiki/IPv4_address_exhaustion">IPv4 address exhaustion</a> most
providers limit the number of IP addresses they’ll allow you to purchase, but I eventually stumbled
across <a href="https://www.vultr.com/">Vultr</a>. While a little sketchy looking, they would sell me as many
IPs as I wanted (for $3/month each) and would let me set up arbitrary reverse DNS entries in their
web console.</p>
<p>I signed up for an account, bought myself a few IPs, set up their reverse DNS, waited a few hours
for DNS to propagate, and had a working proof of concept!</p>
<h1 id="udp">UDP</h1>
<p>With ICMP <code class="language-plaintext highlighter-rouge">traceroute</code> working, it was time to get my implementation working with UDP. <code class="language-plaintext highlighter-rouge">traceroute</code>
typically uses ports around 33400, but that wasn’t guaranteed, so I needed to figure out how to
listen on all UDP ports. Several fruitless Google searches later, and I decided to just give it a
go. This worked great! It turns out that when you’re listening on a raw socket, as I was, ports
disappear entirely, and you see every message!</p>
<p>That was the end of my good luck though, after that I ran into a huge hurdle – how to get the
Linux kernel to stop being helpful. As with ICMP echo requests, the kernel automatically responds
to UDP requests destined to ports with no program listening on them, letting the sender that the
destination port is unreachable. In nearly all cases this is what you want, but in mine it meant I
was racing the kernel to respond to the <code class="language-plaintext highlighter-rouge">traceroute</code> packets.</p>
<p>I thought there would be a <code class="language-plaintext highlighter-rouge">sysctl</code> to disable sending these packets, and I was right, there was! In
FreeBSD. Unfortunately, for an assortment of <a href="https://security.stackexchange.com/questions/22711/is-it-a-bad-idea-for-a-firewall-to-block-icmp/22713#22713">coherent reasons</a> the blackhole <code class="language-plaintext highlighter-rouge">sysctl</code> never made it into
Linux mainstream, which was a bit of a problem. I tried moving my code over to FreeBSD, but I have
no experience with the operating system, so didn’t want to maintain a server running on it.</p>
<p>I spent a few days thinking about the best way to solve this problem, and came up with a pretty
hacky workaround. Linux has a utility called <a href="https://en.wikipedia.org/wiki/Iptables"><code class="language-plaintext highlighter-rouge">iptables</code></a>
that lets you control the kernel-level firewall. I could configure the outbound firewall to block
“destination port unreachable” messages sent by the kernel, while allowing those same packets if
they were sent from userspace.</p>
<p>In practice, this meant setting up <code class="language-plaintext highlighter-rouge">iptables</code> as follows:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># Allow all outbound messages, except "destination port unreachable"</span>
iptables <span class="nt">-A</span> OUTPUT <span class="nt">-p</span> icmp <span class="nt">-m</span> icmp <span class="o">!</span> <span class="nt">--icmp-type</span> port-unreachable <span class="nt">-j</span> ACCEPT
<span class="c"># For outbound "destination port unreachable" messages, drop all messages</span>
<span class="c"># except those sent by root</span>
iptables <span class="nt">-A</span> OUTPUT <span class="nt">-p</span> icmp <span class="nt">-m</span> owner <span class="o">!</span> <span class="nt">--uid-owner</span> root <span class="nt">-j</span> DROP</code></pre></figure>
<p>Though a little unclean, this did successfully get Ubuntu to stop sending “destination port
unreachable” messages for me. I added in support for parsing and generating UDP packets, did some
refactoring, and had an implementation that worked for both UDP and ICMP <code class="language-plaintext highlighter-rouge">traceroute</code>! Success!</p>
<h1 id="running-your-own">Running your own</h1>
<p>A secondary goal of this project was to make it easy for other people to run their own copy of this,
and to make it relatively cheap to do so. Using a single cloud server means it costs around
$5/month, and each IP you use costs $3/month. In theory, it’s possible for you to run this script
on an existing server and using IPs you already own (but don’t care about the reverse DNS records
for), in which case the cost drops to free.</p>
<p>While I think this code should play nicely with other ICMP and UDP traffic on the same box, I make
no guarantees, except that strange things will happen if you’re more than <code class="language-plaintext highlighter-rouge">(64 - number_of_lines)</code>
hops away from a legitimate user.</p>
<p>It’s also worth noting that this code currently only supports IPv4, not IPv6 requests. I’m planning
to add support for the latter soon, but restricting access to only IPv6 would have locked most of
my Australian friends out of playing with this.</p>
<p>You can find the source code, along with instructions on how to set up your server, over on GitHub:
<a href="https://github.com/tetrakai/scuttle">https://github.com/tetrakai/scuttle</a>. Have fun!</p>A bored network engineer caused a bit of a stir on the Internet in early 2013: they set up an IP that when traceroute‘d returned the introductory text of Star Wars: Episode IV. Their original implementation bounced packets between two different routers, with a series of virtual routing tables. I don’t have access to routers that are that configurable, but I thought this was super cool so I set out to make my own version with just a single server in the cloud.SSH for Fun and Profit2016-04-30T00:00:00+00:002016-04-30T00:00:00+00:00https://karla.io/2016/04/30/ssh-for-fun-and-profit<p>In May last year, a new attack on the Diffie Hellman algorithm was released, called
<a href="https://weakdh.org/">Logjam</a>. At the time, I was working on a security team, so it was our
responsiblity to check that none of our servers would be affected. We ran through our TLS config and
decided it was safe, but also needed to check that our SSH config was too. That confused me – where
in SSH is Diffie Hellman? In fact, come to think of it, how does SSH work at all? As a fun side
project, I decided to answer that question by writing a very basic SSH client of my own.</p>
<p>My goal was to connect to an SSH server I owned and to run an arbitrary command, say <code class="language-plaintext highlighter-rouge">cat</code>ing a file.
It sounded easy, maybe the work of a weekend. That estimate ended up being a total lie.</p>
<p>I started off by finding the SSH RFC. Or, well, it turns out there are at least four of them:</p>
<ul>
<li><a href="https://tools.ietf.org/html/rfc4251">RFC 4251</a> - overall architecture</li>
<li><a href="https://tools.ietf.org/html/rfc4252">RFC 4252</a> - user authentication protocol</li>
<li><a href="https://tools.ietf.org/html/rfc4253">RFC 4253</a> - transport protocol</li>
<li><a href="https://tools.ietf.org/html/rfc4254">RFC 4254</a> - connection protocol</li>
</ul>
<p>That looked like way more reading than I was willing to do for this project, so I <code class="language-plaintext highlighter-rouge">nc</code>‘d to port 22
(the standard SSH port) on a server I controlled. It sent back the banner
<code class="language-plaintext highlighter-rouge">SSH-2.0-OpenSSH_6.9p1 Ubuntu-2ubuntu0.1</code>, which was the format described in the transport protocol
RFC. Great, that was where I’d start!</p>
<h1 id="banner-message">Banner message</h1>
<p>I skim read the top section of the RFC, then started at <a href="https://tools.ietf.org/html/rfc4253#section-4.2">section 4.2</a>
which describes this banner message. It’s actually used to negotiate the protocol version the client
and server will be speaking: the first section (<code class="language-plaintext highlighter-rouge">SSH-2.0</code>) describes the SSH version, the second
(<code class="language-plaintext highlighter-rouge">OpenSSH_6.9p1</code>) the software version, and the last (<code class="language-plaintext highlighter-rouge">Ubuntu-2ubuntu0.1</code>) is an optional comment
that can be used to further identify the software.</p>
<p>The transport protocol doesn’t cover who sends their banner first, so you can send yours as soon
as you’ve opened a connection. You can even give this a shot yourself by running <code class="language-plaintext highlighter-rouge">nc $HOSTNAME 22</code>,
and replaying the banner it sends you.</p>
<h1 id="algorithm-negotiation">Algorithm negotiation</h1>
<p>Once you’ve told the server that you speak SSH 2.0, it sends back a pretty long blob:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="s">'''
</span><span class="se">\x14\xd5\x1d</span><span class="s">c</span><span class="se">\x93\x1d\t\xa7\x8d\x0e</span><span class="s">k</span><span class="se">\xc6</span><span class="s">2r</span><span class="se">\xf5\xf3\xe0\x00\x00\x00\x96</span><span class="s">curve25519-sha256@libssh.
org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha2
56,diffie-hellman-group14-sha1</span><span class="se">\x00\x00\x00</span><span class="s">/ssh-rsa,ssh-dss,ecdsa-sha2-nistp256,ssh-ed25519</span><span class="se">\x00</span><span class="s">
</span><span class="se">\x00\x00</span><span class="s">lchacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,
aes256-gcm@openssh.com</span><span class="se">\x00\x00\x00</span><span class="s">lchacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-c
tr,aes128-gcm@openssh.com,aes256-gcm@openssh.com</span><span class="se">\x00\x00\x00\xd5</span><span class="s">umac-64-etm@openssh.com,umac-12
8-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@ope
nssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1</span><span class="se">\x00\x00</span><span class="s">
</span><span class="se">\x00\xd5</span><span class="s">umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha
2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-s
ha2-256,hmac-sha2-512,hmac-sha1</span><span class="se">\x00\x00\x00\x15</span><span class="s">none,zlib@openssh.com</span><span class="se">\x00\x00\x00\x15</span><span class="s">none,zlib@o
penssh.com</span><span class="se">\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00</span><span class="s">
'''</span></code></pre></figure>
<p>There’s definitely some binary encoding going on, but also some readable ASCII strings in there. So
it was back to the RFC to figure out what all that data meant.</p>
<p>Skimming over some text about backwards compatibility brought me to <a href="https://tools.ietf.org/html/rfc4253#section-6">section 6</a>,
which describes the binary packet protocol. Decoding the message with that, and after some more
skimming, I found the <a href="https://tools.ietf.org/html/rfc4253#section-7.1">key exchange algorithm negotiation section</a>.</p>
<p>It turns out that SSH, like many other protocols, doesn’t actually do any cryptography of its own.
Instead, the protocol defines a series of underlying algorithms that are used to guarantee the
secrecy and authenticity of your connection. In fact, SSH negotiates separate algorithms for:</p>
<ul>
<li>key exchange (how you and the server agree on a shared key, that no one else knows),</li>
<li>host key authentication (how you know you’re talking to the right server),</li>
<li>encryption, in each direction (how you stop someone eavesdropping),</li>
<li>MAC, in each direction (how you prevent someone from tampering with your messages), and</li>
<li>compression, in each direction.</li>
</ul>
<p>It also allows negotiation of the human language each side should speak, though as far as I could
tell, this is ignored by OpenSSH.</p>
<p>Once we send back our own list of algorithms, the key exchange begins for real. Well, that’s what
happens if the client and server can agree on algorithms. I had a lot of trouble with the server
deciding my algorithm list was invalid, so I opened up Wireshark and sniffed the negotiation for a
standard SSH client.</p>
<p><img src="/images/posts/wireshark.png" alt="Wireshark debugging client algorithm negotation" width="820px" /></p>
<p>After some debugging, and a lot of packet captures, I settled on the simplest set of algorithms I
could get to work:</p>
<ul>
<li>Diffie Hellman for key exchange,</li>
<li>ECDSA SHA-2 with the NIST P-256 curve for host key authentication (since I couldn’t convince the server to speak anything other than Elliptic Curve crypto for this),</li>
<li>AES-128 CTR as an encryption algorithm,</li>
<li>HMAC SHA-1 as the MAC algorithm, and</li>
<li>“none” as my compression algorithm.</li>
</ul>
<h1 id="exchanging-keys-with-diffie-hellman">Exchanging keys with Diffie Hellman</h1>
<p>Now I was getting to the challenging part – actual key exchange! This is where the client and
server agree on a secret that’s used for the remainder of the session. I’d decided to use Diffie
Hellman, both because it was what got this whole thing started, and because it was one of the only
algorithms my server supported that was documented in the original RFC.</p>
<p>Diffie Hellman’s security is derived from the hardness of the <a href="https://en.wikipedia.org/wiki/Discrete_logarithm">discrete logarithm problem</a>.
Essentially, if you raise one number to the power of another (mod a third), then given the base and
the result, it’s very difficult to find the exponent. In this particular case, I’d decided to use
<a href="https://tools.ietf.org/html/rfc3526#section-3">Diffie Hellman group 14</a>, which is simply a
particular set of base, mod, and field size numbers. Implementing this in Python is pretty easy –
you generate a random number, run the <code class="language-plaintext highlighter-rouge">pow</code> function with the paramaters given, and you have a
shared key.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1"># Diffie Hellman key exchange (group 14)
# g is the base, p is the mod, and q is the field size
</span><span class="n">g</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">p</span> <span class="o">=</span> <span class="mh">0xFFFFFFFFFFFFFFFFC90FDAA22168</span><span class="p">...</span>
<span class="n">q</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">2048</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">SystemRandom</span><span class="p">().</span><span class="n">randint</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">q</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">e</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>
<span class="c1"># Send e to the server
# Receive f from the server
</span>
<span class="n">shared_key</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span></code></pre></figure>
<h1 id="verifying-the-servers-identity">Verifying the server’s identity</h1>
<p>Once I’d generated a shared key, I needed to make sure I was actually talking to the right server,
not an imposter. In SSH, that’s done by having the server sign all data sent so far in the
connection with its private key, and having the client verify that with a known public key.</p>
<p>Unfortunately for me, the server was running a fairly new version of OpenSSH, which meant it would
only verify its identity using ECDSA. I don’t fully understand elliptic curve cryptography, but
helpfully Python has an <a href="https://pypi.python.org/pypi/ecdsa">ECDSA module</a> that could do the hard
work for me. Unhelpfully, it takes signatures in a different, and as far as I can tell undocumented,
key format.</p>
<p>With some guesswork, I discovered that this format seemed to be just the raw bytes of the <code class="language-plaintext highlighter-rouge">r</code> and
<code class="language-plaintext highlighter-rouge">s</code>, the two components that make up an ECDSA key. Unfortunately, these both needed to be 32 bytes
long, and for some reason, I’d occasionally find that they had an extra byte! This meant that
signature verification would only work around one in every four times.</p>
<p>A closer reading of the spec proved helpful here, in particular of <a href="https://tools.ietf.org/html/rfc4251">RFC 4251</a>,
the “overall architecture” RFC that I’d skipped earlier. <code class="language-plaintext highlighter-rouge">r</code> and <code class="language-plaintext highlighter-rouge">s</code> were being sent as <code class="language-plaintext highlighter-rouge">mpints</code>, or
“multiple precision integers”. I’d assumed that these were unsigned integers in network byte order
(that is, big endian), however they’re actually encoded using two’s complement. This means that the
first bit of the number determines the sign, so to encode a 32 bit unsigned integer with its high
bit set, you need to add a leading <code class="language-plaintext highlighter-rouge">0x00</code> byte.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">twos_complement</span><span class="p">(</span><span class="n">byte_array</span><span class="p">):</span>
<span class="n">byte_array</span> <span class="o">=</span> <span class="p">[</span><span class="n">bitflip_byte</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">byte_array</span><span class="p">]</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">byte_array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="s">'</span><span class="se">\xff</span><span class="s">'</span><span class="p">:</span>
<span class="n">byte_array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="s">'</span><span class="se">\x00</span><span class="s">'</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">byte_array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'B'</span><span class="p">,</span> <span class="n">struct</span><span class="p">.</span><span class="n">unpack</span><span class="p">(</span><span class="s">'B'</span><span class="p">,</span> <span class="n">byte_array</span><span class="p">[</span><span class="n">i</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">return</span> <span class="n">byte_array</span>
<span class="k">def</span> <span class="nf">bitflip_byte</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="k">return</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'B'</span><span class="p">,</span> <span class="p">(</span><span class="o">~</span><span class="n">struct</span><span class="p">.</span><span class="n">unpack</span><span class="p">(</span><span class="s">'B'</span><span class="p">,</span> <span class="n">data</span><span class="p">)[</span><span class="mi">0</span><span class="p">])</span> <span class="o">%</span> <span class="mh">0x100</span><span class="p">)</span></code></pre></figure>
<p>Implementing two’s complement in Python was more involved than it probably should have been, but
once I was parsing <code class="language-plaintext highlighter-rouge">mpints</code> correctly, I could reliably verify signatures! These were being verified
against the public key the server presents, but I still needed to check that the public key really
was for that server.</p>
<p>The SSH spec is intentionally lenient here – all current key distribution systems have substantial
drawbacks, so the default is to allow users to accept arbitrary public keys the first time they
connect, and then warn them if these keys change. That’s the “<code class="language-plaintext highlighter-rouge">Are you sure you want to continue
connecting (yes/no)?</code>” you see whenever you try to SSH to a new server!</p>
<p>I went down a bit of a rabbit hole here, duplicating the behavior of my local OpenSSH client: adding
new host keys to <code class="language-plaintext highlighter-rouge">~/.ssh/known_hosts</code> file, and checking for existing keys there. However, with that
done, key exchange was over, and we were ready to move on to actually sending data!</p>
<h1 id="encrypting-and-authenticating-messages">Encrypting and authenticating messages</h1>
<p>From here on in, all the messages sent between the client and server were encrypted and
authenticated. That rendered Wireshark useless for debugging, since it works by intercepting the
connection.</p>
<p>This was a problem, because my initial packets to the server were met with immediate disconnects,
and I’d now lost my main means of debugging. I banged my head against the wall for a while, then at
the suggestion of a friend, decided to turn the server’s OpenSSH log verbosity way up. I bumped the
<code class="language-plaintext highlighter-rouge">LogLevel</code> in <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code> to <code class="language-plaintext highlighter-rouge">DEBUG3</code>, and suddenly I was getting helpful error
messages!</p>
<p>The bug turned out to be minor (a missing value for key derivation), however this led me to
the most fun part of the SSH spec, the hard-coded alphabet! As already mentioned, SSH relies on a
number of other cryptographic primitives, each of which needs its own separate key. In fact, SSH
needs six separate pseudo-random values, each of which it derives from the originally negotiated
shared key.</p>
<p>SSH uses a well-known property of cryptographic hash functions, called the avalanche effect, to
derive these keys. This property says that small changes in input value lead to large changes in
output value. By adding a single extra byte to the shared key, we can generate a completely
different key. In the case of SSH, the spec actually hard-codes the ASCII values “A” through “F” for
this. Nothing is wrong with that from a security perspective, but it’s adorable to know that those
values exist in every SSH connection you make:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">Initial IV client to server</span><span class="pi">:</span> <span class="s">HASH(K || H || "A" || session_id)</span>
<span class="s">(Here K is encoded as mpint and "A" as byte and session_id as raw</span>
<span class="s">data. "A" means the single character A, ASCII 65).</span></code></pre></figure>
<h1 id="user-authentication">User authentication</h1>
<p>With encryption and authentication going, I now had a working transport layer to start building on!</p>
<p>Like the OSI network model, SSH is also layered - inside the basic transport protocol, which
guarantees confidentiality and authenticity, we can run other, more complex protocols. The first one
of these that we need to run in order to do anything is the “ssh-userauth” protocol. This
authenticates the client to the server, and it’s the reason why you pass a username as part of
SSHing to a server.</p>
<p>There are several ways to authenticate an SSH user, some of which you’ve likely used before:
password, public key, and host-based authentication are all supported. For my client, I decided to
go with public key authentication, since that’s what I use day to day. To do this, I needed to sign
a message with the private key corresponding to a public key that I’d granted access to the server.
In practice, this meant cracking open my <code class="language-plaintext highlighter-rouge">~/.ssh/id_rsa</code> file! Or, trying to – my private key is
stored in a password-protected file, and the version of PyCrypto I was using didn’t seem to be able
to parse the format.</p>
<p>I generated a new key pair (this time stored in plaintext) added the public half as an authorized
key to the server I was using, and then tried authenticating. No luck. I skimmed through the
<a href="https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey.RSA._RSAobj-class.html#encrypt">PyCrypto RSA encrypt documentation</a> a few times before noticing the very obvious <strong>Attention</strong> banner that said I shouldn’t be calling this function
unless I really knew what I was doing. It turns out that so-called “textbook RSA” has a number of
<a href="https://crypto.stackexchange.com/questions/1448/definition-of-textbook-rsa">undesirable properties</a>,
and that you nearly always want to pad your data. With the right padding in place, I got an
<code class="language-plaintext highlighter-rouge">SSH_MSG_USERAUTH_SUCCESS</code> message, and we were away!</p>
<h1 id="getting-a-shell">Getting a shell</h1>
<p>Now that I’d authenticated, there was only one step to go, launching a shell! Like all things
SSH, this was a little more complicated than it first sounded. SSH uses a concept called
“channels” – essentially, it’s possible to run multiple logical connections over the one transport
connection. This means you could, for example, run an SSH shell, forward X over SSH, and forward a
port over SSH, all with the same connection.</p>
<div style="text-align: center; display: block; margin-left: auto; margin-right: auto; width: 480px">
<img src="/images/posts/ssh_layers.png" width="100%" style="float: none; padding: 0" alt="SSH layers" />
</div>
<p>So, to get to a shell, the first thing I needed to do was to open a channel. Since I’d be
dynamically interacting with the channel, I needed to open an interactive session, then pivot this
into a shell. Initially, I’d open a channel with the “session” type, then I’d request that a
particular program be launched on the server-side. In this case, I requested a shell, but you can
also run arbitrary commands on the server, pass environment variables, or
<a href="https://tools.ietf.org/html/rfc4254#section-6.2">any number of other fun things</a>.</p>
<p>With a shell launched server-side, I finally got back the message I’d been hoping for – the Ubuntu
welcome banner! I was surprised to see that a prompt wasn’t being sent over the wire, but wrote a
simple read-eval-print loop that let me run arbitrary commands on my remote server. I successfully
<code class="language-plaintext highlighter-rouge">cat</code>ed a file, and could also use pipes to create my own on the server. Mission accomplished!</p>
<h1 id="finishing-up">Finishing up</h1>
<p>I’ve refactored my code a little to make it easier to read, but you can find my client, along with
inline documentation, over on GitHub: <a href="https://github.com/tetrakai/ssh_client">https://github.com/tetrakai/ssh_client</a>.</p>
<p>My “weekend” project ended up taking more like weeks, but I learned a ton about how SSH worked in
the process! I hadn’t realized that the protocol was as layered as it actually was, that client
authentication happened completely separately to host authentication, or that it was possible to
multiplex connections. Not to mention, I finally figured out where the Diffie Hellman was!</p>In May last year, a new attack on the Diffie Hellman algorithm was released, called Logjam. At the time, I was working on a security team, so it was our responsiblity to check that none of our servers would be affected. We ran through our TLS config and decided it was safe, but also needed to check that our SSH config was too. That confused me – where in SSH is Diffie Hellman? In fact, come to think of it, how does SSH work at all? As a fun side project, I decided to answer that question by writing a very basic SSH client of my own.Tim Tam Logistics2016-03-30T00:00:00+00:002016-03-30T00:00:00+00:00https://karla.io/2016/03/30/tim-tam-logistics<p>As an Australian living in the US, I feel it’s my duty to introduce the treats of
my childhood to my co-workers: <a href="http://au.redfrogs.com/files/styles/box-image/public/featured-images/frog.jpg">red frogs</a>,
<a href="http://cdn1.bigcommerce.com/server1700/6d68e/products/3454/images/1987/allens-fantales-loose__59891.1281000020.1280.1280.jpg?c=2">Fantales</a>,
and <a href="http://www.healthguru.sg/wp-content/uploads/2012/07/4._nestle-milo-tin-400g.jpg">Milo</a>
have all made appearances. However, far and away the mostly highly voted treat
has been Tim Tams. Unfortunately, as an imported product, Tim Tams are pretty
expensive in the US - over US$8 a packet
<a href="http://www.amazon.com/Arnotts-Australia-Choose-Original-Chocolate/dp/B00NEA6TAI/">on Amazon</a>.
It’s not immediately clear where this cost comes from, so I decided to figure
out if I could sell them for less.</p>
<p><img src="/images/posts/timTams.png" alt="Original Tim Tams, picture from Arnott's site" /></p>
<p>Tim Tams are manufactured in a number of countries across the Asia Pacific
region, including not just Australia and New Zealand, but also Indonesia, Japan,
and Singapore. They are, however, customized slightly for each of these markets,
so let’s assume for the sake of argument that we wish to purchase Australian
Tim Tams.</p>
<p>The first thing we need to do is find out the wholesale price for a packet.
This information is a little difficult to come by, so instead we’ll extrapolate
from retail prices, since we’re only trying to get a ballpark figure anyway.
There are two large supermarket chains in Australia, Woolworths and Coles, but
both sell a full-priced packet of 11 biscuits for the same amount: AU$3.50,
(currently US$2.63). However, Tim Tams are so popular that one of the two chains
typically has them on special at any point in time, usually for AU$2.50
(currently US$1.88). Given the frequency of these sales, I think it’s reasonable
to say that the supermarkets aren’t making a loss on them at that price, so
we’ll use a generous wholesale price of US$1.88.</p>
<p>Now that we’ve figured out how much it costs to buy an individual packet of Tim
Tams, we need to decide how many packets we’re going to buy. Assuming that we
have sufficient initial capital (say from an IndieGoGo campaign, or similar),
our main constraint is going to be shipping.</p>
<p>The cheapest way to get a lot of things from Australia to the US is on a ship,
so we need to determine what size shipping container we want. It turns out that
shipping containers are always 8 x 8ft wide and tall, and can be 20ft, 40ft, or
45ft long. Let’s say that we don’t want to have too high an initial cost, so
we’ll use the smallest available shipping container size. That means we’ll have
an 8 x 8 x 20ft, or 2438 x 2438 x 6096mm, box at our disposal. We’ll give
ourselves some wiggle room and call that 2200 x 2200 x 5800mm.</p>
<p>Tim Tam packets themselves are 35 x 75 x 220mm, and come in boxes of 24 packets,
layered 6 across and 4 tall. That gives us dimensions of 140 x 450 x 220mm for
each box, which we’ll round up to 150 x 500 x 250, for air and packaging.
Stacking boxes, we can say we’ll fit 14 boxes high, 5 boxes across, and 23 boxes
deep. That’s a total of 1,610 boxes, or 38,640 packets of Tim Tams (containing
over 425,000 individual biscuits!).</p>
<p>With a handle on our volume, we can start to talk costs. We said that we’d
assume a wholesale price of US$1.88 for each packet, so buying enough stock for
our container is going to cost US$73,000 (to the nearest $1000).</p>
<p>Once we’ve bought that stock, we need to get it to a port, where we can load it
into a shipping container. Let’s say we choose to rent a truck for that - a
moving truck will be a little small, so we’ll use two. Each will cost around
AU$500, so we’ll budget AU$1000 for the vehicles themselves. We’ll also need
drivers for the trucks, but since they’re just moving trucks, they can be driven
by anyone. Let’s say we use AirTasker (an Australian TaskRabbit-alike), and offer
AU$30/hr, well over the Australian minimum wage. For two people for an eight
hour day, that will cost us just under AU$500. So we’re looking at AU$1500, or
US$1200 for transportation from the point of sale to the port.</p>
<p>Ideally, we’d like a port in one of the larger Australian cities, to give us
more choice of supermarket to source our Tim Tams from. Since we’re shipping to
the US, we’ll presumably want a port on the Eastern side of the country. Playing
with the rates on <a href="http://worldfreightrates.com/">worldfreightrates.com</a>, it
doesn’t seem to make a difference which ports we ship to and from on the
Australian east coast / US west coast, so we’ll use Sydney and Oakland, since
they’re the ports I’m most familiar with. To keep our Tim Tams in the best
possible shape, we’ll pay for a refrigerated container, and since it’s a large
investment, also insurance. We get a quote of US$1950, which we’ll round to
US$2000.</p>
<p>So, our biscuits have made it to the US! Well, not quite - we still need to pay
import taxes. We’ll use <a href="http://www.dutycalculator.com/">dutycalculator.com</a>
to figure out how much we need to pay to import our “Biscuits | coated with
chocolate” to California. Helpfully, the US and Australia have a bilateral free
trade agreement, so we only end up needing to pay US$250 for duty!</p>
<p>So far, we’re at a total cost of US$76,450, or US$1.97 per packet of Tim Tams.
Things are looking pretty promising for us being able to beat the market rate of
US$8 per packet!</p>
<p>However, we said that we’d fund this with some kind of crowdfunding campaign.
That means we’ll now need to distribute our Tim Tams to our backers, which will
mean shipping to an assortment of addresses within the United States. We’d
rather not expend too much effort on this, and we’d like a way to continue
offering shipping on an ongoing basis, so we’ll use Fulfillment by Amazon.</p>
<p>We’ll need to watch out for a number of requirements Amazon specify
<a href="http://www.amazon.com/gp/help/customer/display.html?nodeId=200243250">here</a>.
We’ll have to add a shipping label to each of the boxes of Tim Tams, so that
they can be correctly received, in addition to labelling each box with a
sticker that notes the expiration dates on the boxes are in DD-MM-YYYY format.
Let’s assume that we’ll get our two AirTaskers to deal with that labelling back
in Sydney, and that that costs us nothing.</p>
<p>We then need to get our shipment over to Amazon. Like most things Amazon, the
location of their fulfillment centers isn’t widely published, but if
<a href="http://blog.taxjar.com/amazon-sort-centers-locations/">this blog</a> is to be
believed, there’s a center in Alameda County, just down the road from Oakland’s
port. Trucking costs in Sydney and Oakland are similar, so we’ll budget another
US$1200 for transporting the goods to Amazon.</p>
<p>The last thing for us to figure out is how much Amazon will charge us for the
privilege of shipping our biscuits. We can find Fulfillment by Amazon fees
<a href="http://www.amazon.com/gp/help/customer/display.html/?nodeId=201119430">here</a>.
Assuming that we ship every packet as an individual unit, and that we use
standard shipping, we get a cost of $4.75 for order handling, $0.75 for pick and
pack, and $0.45 for weight, giving us a total cost of $5.95 for shipping each
packet around the US.</p>
<p>That means each of our Tim Tam packets is going to cost $7.92, surprisingly
close to the $8 price we originally saw! That surprised me, because I assumed
that the Amazon sellers would be making a larger profit than that, and certainly
would have more foreign exchange buffer built into their prices.</p>
<p>However, looking more closely, I noticed that that listing is actually a
<em>2 pack</em> of Tim Tams. Making the same optimization ourselves gives us a total
cost of $5.95 + $1.97*2 = $9.89, or a 60% markup - much better. There are
definitely price optimizations to be made in these calculations, but the
ballpark seems right.</p>
<p>So there you have it - the cost of Tim Tams on Amazon in the US has nothing to
do with getting them across the Pacific. In fact, the cost of moving Tim Tams
12,000km across the Pacific was just 12c per packet! A quarter of the remaining
cost was the wholesale price of the biscuits, and the remaining 75% was getting
the biscuits to individual houses in the US. Who knew domestic shipping was
so expensive!</p>As an Australian living in the US, I feel it’s my duty to introduce the treats of my childhood to my co-workers: red frogs, Fantales, and Milo have all made appearances. However, far and away the mostly highly voted treat has been Tim Tams. Unfortunately, as an imported product, Tim Tams are pretty expensive in the US - over US$8 a packet on Amazon. It’s not immediately clear where this cost comes from, so I decided to figure out if I could sell them for less.Puzzles with coreutils (Part 2)2015-06-28T00:00:00+00:002015-06-28T00:00:00+00:00https://karla.io/2015/06/28/brain-teaser-2<p>On another free weekend afternoon, I decided to finish off the rest of the
coreutils brainteasers I started <a href="/2015/05/28/brain-teaser.html">last month</a>. This
time I learned more about Linux audio, bash substring replacement, and the
assortment of flags that <code class="language-plaintext highlighter-rouge">ls</code> supports.</p>
<h1 id="directly-route-your-microphone-input-over-the-network-to-another-computers-speaker">Directly route your microphone input over the network to another computer’s speaker</h1>
<p>While I’m not sure if this is the right solution, I ended up doing this using
ALSA. This, of course, required setting up two Linux VMs, but once I had proved
to be fairly simple:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh">machine2 <span class="nv">$ </span>ifconfig<span class="p">;</span> nc <span class="nt">-l</span> 4321 | aplay
machine1 <span class="nv">$ </span>arecord <span class="nt">-f</span> <span class="nb">cd</span> | nc <span class="nv">$OTHER_IP</span> 4321</code></pre></figure>
<p>In brief, <code class="language-plaintext highlighter-rouge">arecord</code> writes the mic input to stdout, while <code class="language-plaintext highlighter-rouge">nc</code> pipes the output
over the network to the second machine, which then pipes that input back to
<code class="language-plaintext highlighter-rouge">aplay</code>, which plays the input.</p>
<p>Messing around with this was pretty fun - the <code class="language-plaintext highlighter-rouge">-f</code> argument specifies the output
format of the recording. Leaving it off caused the output to be poor quality,
almost like a radio. More experimentation showed that this was because <code class="language-plaintext highlighter-rouge">-f cd</code>
causes <code class="language-plaintext highlighter-rouge">arecord</code> to use CD quality audio - 16 bit, stereo, 44.1 kHz recording.
With no format argument, <code class="language-plaintext highlighter-rouge">arecord</code> defaults to much lower quality audio - 8 bit,
mono, 8kHz, in fact.</p>
<h3 id="replace-all-spaces-in-a-filename-with-underscore-for-a-given-directory">Replace all spaces in a filename with underscore for a given directory</h3>
<p>This challenge turned out to be trivial on Linux, and a bit more frustrating on
OS X.</p>
<p>Linux has the handy <code class="language-plaintext highlighter-rouge">rename</code> command, which does exactly what we want here -
takes a Perl regex and uses it to move a file:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh">rename <span class="s1">'s/ /_/g'</span> <span class="k">*</span></code></pre></figure>
<p>That was too easy though, so I decided to figure out how to do the same thing on
a BSD-based system:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">ls</span> | <span class="nb">grep</span> <span class="s1">' '</span> | <span class="k">while </span><span class="nb">read </span>file<span class="p">;</span> <span class="k">do </span><span class="nb">mv</span> <span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">file</span><span class="p">// /_</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">done</span></code></pre></figure>
<p>Here, we filter to find files that actually contain a space (so that we don’t
unnecessarily move files to the same location), then we pipe those results into
a while loop that does what we want.</p>
<p>Cool things I learned here:</p>
<ul>
<li>It’s possible to pipe things into a <code class="language-plaintext highlighter-rouge">while</code> loop! I finally understand how
all those scripts that use <code class="language-plaintext highlighter-rouge">read</code> work</li>
<li>Bash has substring replacement, and it’s awful. <code class="language-plaintext highlighter-rouge">"${file/ /_}"</code> replaces the
<em>first</em> space in <code class="language-plaintext highlighter-rouge">$file</code> with an underscore. How do you replace globally? Add
an extra slash to the first bit (of course!). This is why I usually use
Python for more complex tasks</li>
</ul>
<h3 id="report-the-last-ten-errant-accesses-to-the-web-server-coming-from-a-specific-ip-address">Report the last ten errant accesses to the web server coming from a specific IP address</h3>
<p>It was unclear what errant and accesses meant here, so I assumed that we were
just trying to find all requests made to a web server. I also assumed that we
were using <code class="language-plaintext highlighter-rouge">nginx</code>.</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">cd</span> /var/log/nginx<span class="p">;</span> zgrep <span class="nv">$IP</span> <span class="si">$(</span><span class="nb">ls</span> <span class="nt">-vr</span> access.<span class="k">*</span><span class="si">)</span> | <span class="nb">tail</span> <span class="nt">-n</span> 10</code></pre></figure>
<p>The hardest part of this challenge wasn’t figuring out the IP accesses (which
were trivially <code class="language-plaintext highlighter-rouge">grep</code>-able), but figuring out how to handle the compressed log
format <code class="language-plaintext highlighter-rouge">nginx</code> uses. We needed to replace normal <code class="language-plaintext highlighter-rouge">grep</code> uses with <code class="language-plaintext highlighter-rouge">zgrep</code>, which
can handle gzipped files, and then needed to do some <code class="language-plaintext highlighter-rouge">ls</code> magic to fix the
ordering - log lines are stored from least to most recent inside the file, but
<code class="language-plaintext highlighter-rouge">access.log</code> is more recent than <code class="language-plaintext highlighter-rouge">access.log.1.gz</code>, and both are more recent
than <code class="language-plaintext highlighter-rouge">access.log.2.gz</code>.</p>
<p>Passing <code class="language-plaintext highlighter-rouge">-r</code> to <code class="language-plaintext highlighter-rouge">ls</code> reversed the order of its output, which meant that the log
lines would now be searched from the least to most recent, across the files.
There was one extra challenge though - <code class="language-plaintext highlighter-rouge">ls</code> sorts lexicographically, not
numerically. This means that <code class="language-plaintext highlighter-rouge">access.log.10.gz</code> sorts after <code class="language-plaintext highlighter-rouge">access.log.1.gz</code>
but before <code class="language-plaintext highlighter-rouge">access.log.2.gz</code>. That’s not what we want! Passing <code class="language-plaintext highlighter-rouge">-v</code> to <code class="language-plaintext highlighter-rouge">ls</code>
tells it to sort naturally by version numbers within the text - i.e. it will
sort numerically when it finds a number.</p>
<p>We put these together to get all accesses from a given IP, then cut down to the
most recent ten.</p>
<h3 id="wrap-up">Wrap up</h3>
<p>Overall, these tasks were pretty interesting. Some of the solutions were
immediately obvious to me, while others I would have had no idea how to solve,
even if I were allowed to use Python.</p>
<p>While I’m not sure I’ll ever actually use these pipelines, solving the problems
definitely gave me a better appreciation for Unix’s “everything is really a
file philosophy.” Unfortunately, I still retain my negative feelings towards
bash for anything that can’t be expressed in a single loop-free
non-subshell-containing command.</p>On another free weekend afternoon, I decided to finish off the rest of the coreutils brainteasers I started last month. This time I learned more about Linux audio, bash substring replacement, and the assortment of flags that ls supports.Puzzles with coreutils (Part 1)2015-05-28T00:00:00+00:002015-05-28T00:00:00+00:00https://karla.io/2015/05/28/brain-teaser<p>Reading through an article, <a href="http://matt.might.net/articles/what-cs-majors-should-know/">What every computer science major should know</a>,
I came across a couple of interesting coreutils brain teasers under “The Unix
philosophy” to play with of an evening. They turned out to be a great way to
learn about some more tools, and to brush up on some of those I already knew.</p>
<h1 id="find-the-five-folders-in-a-given-directory-consuming-the-most-space">Find the five folders in a given directory consuming the most space.</h1>
<p>This one was pretty simple, though I still learned a new trick! First, I count
the amount of space each directory is using, then sort from largest to smallest,
strip the first line (which will be <code class="language-plaintext highlighter-rouge">.</code>), then cut out the directory sizes.</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">du</span> | <span class="nb">sort</span> <span class="nt">-nr</span> | <span class="nb">tail</span> <span class="nt">-n</span> +2 | <span class="nb">cut</span> <span class="nt">-f2-</span></code></pre></figure>
<p>I couldn’t find a good way to display the human readable size without counting
twice, though this <a href="http://serverfault.com/a/156648">serverfault answer</a>
suggests that it might be possible with GNU coreutils >= 7.5.</p>
<p>But, I did learn something new! I wasn’t aware that you could pass <code class="language-plaintext highlighter-rouge">+x</code> to
<code class="language-plaintext highlighter-rouge">tail</code> to have it start from the xth line.</p>
<h1 id="report-duplicate-mp3s-by-file-contents-not-file-name-on-a-computer">Report duplicate MP3s (by file contents, not file name) on a computer.</h1>
<p>This one was quite a bit more complicated! At a high level, it seemed like what
I wanted here was to hash each file, then compare hashes across all files to
find duplicates.</p>
<p>Hashing each file is trivial, as is figuring out which of the hashes are
duplicates, but mapping them back to files is a little difficult (given that I’d
also decided I was only going to use coreutils for this!)</p>
<p>The solution I ended up with is far from ideal - I end up hashing all the files
twice. My original solution also contained a second call to <code class="language-plaintext highlighter-rouge">xargs sh</code> to get
a subshell, till Theo showed me this cool bash-only subshell trick!</p>
<p>The core idea is that we find hashes for each file, cut out all but those that
are repeated at least once, then search for files with any of those hashes.</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh">find <span class="nb">.</span> <span class="nt">-name</span> <span class="s1">'*.mp3'</span> | xargs shasum | <span class="se">\</span>
<span class="nb">grep</span> <span class="nt">-f</span> <<span class="o">(</span>find <span class="nb">.</span> <span class="nt">-name</span> <span class="s1">'*.mp3'</span> | xargs shasum | <span class="nb">cut</span> <span class="nt">-f1</span> <span class="nt">-d</span><span class="s1">' '</span> | <span class="nb">sort</span> | <span class="nb">uniq</span> <span class="nt">-d</span><span class="o">)</span> | <span class="se">\</span>
<span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f3-</span></code></pre></figure>
<p>I learned a bunch from this task!</p>
<ul>
<li>I finally bothered to look up why <code class="language-plaintext highlighter-rouge">find</code> has such strange syntax, and as a
consequence never works as I expect. Turns out, the <code class="language-plaintext highlighter-rouge">-name</code> part is a
pattern, which has to come after the folder you’re searching in.</li>
<li>I discovered that <code class="language-plaintext highlighter-rouge">find</code> supports <code class="language-plaintext highlighter-rouge">-exec</code> as an option, and that it’s
terrifying and should only be used with lots of care.</li>
<li>While looking these up, I discovered that a better command to find your IP
address than just running <code class="language-plaintext highlighter-rouge">ifconfig</code> is <code class="language-plaintext highlighter-rouge">ipconfig getifaddr en0</code>. Relatedly,
<code class="language-plaintext highlighter-rouge">ipconfig</code> is a thing on non-Windows systems!</li>
<li><code class="language-plaintext highlighter-rouge">xargs</code> is much less intimidating than I always thought it was, it turns out
it’s really easy to use!</li>
<li><code class="language-plaintext highlighter-rouge">grep</code> can take patterns from a file! I’d seen this before, but it totally
slipped my mind till Theo brought it up</li>
<li><code class="language-plaintext highlighter-rouge">tr '\\n' ''</code> is the best way to get rid of newlines from stdin (from an
earlier solution)</li>
<li><code class="language-plaintext highlighter-rouge"><(...)</code> is a magic bash trick to create a pseudo-file, that will act as a
file object, without actually touching disk (via the magic of <code class="language-plaintext highlighter-rouge">/dev/fd/</code>)</li>
</ul>
<h1 id="take-a-list-of-names-whose-first-and-last-names-have-been-lower-cased-and-properly-recapitalize-them">Take a list of names whose first and last names have been lower-cased, and properly recapitalize them.</h1>
<p>I did some research into this, and discovered that I think this is only possible
with the GNU version of <code class="language-plaintext highlighter-rouge">sed</code>. Unfortunately, I don’t have that, so I resorted
to cheating:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh">python <span class="nt">-c</span> <span class="s1">'import sys; sys.stdout.write(sys.stdin.read().title())'</span></code></pre></figure>
<h1 id="find-all-words-in-english-that-have-x-as-their-second-letter-and-n-as-their-second-to-last">Find all words in English that have x as their second letter, and n as their second-to-last.</h1>
<p>This one was also very familiar to me - I often use my computer to cheat at
scrabble.</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh">egrep <span class="s1">'^.[xX].*[nN].$'</span> /usr/share/dict/words</code></pre></figure>
<p>This does break in the case of the second-to-last letter preceding the second
letter, but since this can only happen in the “xn” case (and that isn’t a word),
I don’t think it’s a huge deal.</p>Reading through an article, What every computer science major should know, I came across a couple of interesting coreutils brain teasers under “The Unix philosophy” to play with of an evening. They turned out to be a great way to learn about some more tools, and to brush up on some of those I already knew.