cpu
Use the processor's AES and carry-less multiply instructions, and ask what the processor offers.
import cpu
Modern processors can run one round of AES, or multiply two 64-bit values without carries, in a single instruction. This package makes those instructions available on u128 values, and tells you at run time whether the machine in front of you has them. tls uses it for AES-GCM, so the VPN and proxy protocols and the TLS record layer run on the fast path by themselves; most programs never need to call this package.
The instructions are real only when the program is built with the LLVM backend, which every release build uses. On the C backend, the native backend, the interpreter and in the browser every function still works, but as a slow software model of the instruction, and HasHardwareCrypto is false so that callers choose their own faster code. The software model gives the same bytes, which is what lets a test run the same data both ways.
Things to know:
- Test first, then call. The
Hwfunctions work everywhere, but only whereHasHardwareCryptois true are they one instruction each. - The switch is read when a cipher is made.
SetHardware(false)affects the nextAeadNewor one shot call, not a context that already exists. The environment variableSALAM_NO_HWCRYPTOdoes the same for a whole run. HwClmulwants a literal. Its third argument becomes part of the instruction, so write0,1,16or17there, not a variable.- Constant time. The instructions take the same time for any data, which the table based software AES cannot promise.
Check the fast path against the slow one
SetHardware turns the fast path off, so one program can seal the same message both ways and compare. This is how the tests of the package check that the two paths give the same bytes. On a machine without the instructions both runs take the software path and the answer is still the same.
import cpu
import mem
import tls
func hex(p: void*, n: int): str:
b := p as u8*
digits := "0123456789abcdef"
mut s := ""
repeat n in i:
v := b[i] as int
s += digits.substr(v >> 4, 1)
s += digits.substr(v & 15, 1)
end
ret s
end
func seal(): str:
key := mem.AllocateZeroed(16 as u64)
nonce := mem.AllocateZeroed(12 as u64)
msg := mem.AllocateZeroed(32 as u64)
out := mem.AllocateZeroed(48 as u64)
tls.AesGcmSealRaw(key, 16, nonce, null, 0, msg, 32, out)
r := hex(out, 48)
mem.Free(key)
mem.Free(nonce)
mem.Free(msg)
mem.Free(out)
ret r
end
func main:
cpu.SetHardware(true)
fast := seal()
cpu.SetHardware(false)
slow := seal()
println "same bytes:", fast == slow
println "tag:", fast.substr(64, 32)
endThe tag line is the tag of 32 zero bytes under an all zero key and nonce; the algorithm fixes it, so it is the same on every machine.
Which function for which job
| Job | Use |
|---|---|
| Is the fast path available and on? | HasHardwareCrypto |
| Which family of processor? | HasAesNi (x86-64), HasArmCrypto (AArch64), or the bits of Features |
| Force the software path | SetHardware(false) or SALAM_NO_HWCRYPTO=1 |
| One round of AES on a block | HwAesEnc and HwAesEncLast |
| A carry-less product, as in GHASH | HwClmul |
| Move a block in and out of memory | HwLoad and HwStore |
| Reverse the bytes of a block, or swap its halves | HwBswap and HwSwap64 |
Constants and variables
FEATURE_HW_CRYPTO
const FEATURE_HW_CRYPTO: i32 = 1Features that says the AES and carry-less multiply instructions are there and the program was built to use them.import cpu
import mem
func show(label: str, x: u128):
b := mem.AllocateZeroed(16 as u64)
cpu.HwStore(b, x)
p := b as u8*
digits := "0123456789abcdef"
mut s := ""
repeat 16 in i:
v := p[i] as int
s += digits.substr(v >> 4, 1)
s += digits.substr(v & 15, 1)
end
println label, s
mem.Free(b)
end
func main:
b := mem.AllocateZeroed(16 as u64)
p := b as u8*
repeat 16 in i:
p[i] = (i * 17) as u8
end
x := cpu.HwLoad(b)
show("load ", x)
show("round", cpu.HwAesEnc(x, x))
show("last ", cpu.HwAesEncLast(x, x))
show("mul ", cpu.HwClmul(x, x, 0))
show("mulhi", cpu.HwClmul(x, x, 17))
show("swap ", cpu.HwSwap64(x))
show("bswap", cpu.HwBswap(x))
cpu.SetHardware(false)
println "off:", cpu.HasHardwareCrypto()
cpu.SetHardware(true)
f := cpu.Features()
ok := (f & cpu.FEATURE_HW_CRYPTO) != 0
println "on matches the processor:", cpu.HasHardwareCrypto() == ok
println "families agree:", cpu.HasAesNi() == (ok and (f & cpu.FEATURE_X86) != 0), cpu.HasArmCrypto() == (ok and (f & cpu.FEATURE_ARM64) != 0), cpu.HasPclmul() == cpu.HasAesNi()
mem.Free(b)
endFEATURE_X86
const FEATURE_X86: i32 = 2Features that says the processor is x86-64 (AES-NI and PCLMULQDQ).The example for this item is shown with the ones above: cpu-Intrinsics.salam
FEATURE_ARM64
const FEATURE_ARM64: i32 = 4Features that says the processor is AArch64 (the Crypto Extensions).The example for this item is shown with the ones above: cpu-Intrinsics.salam
Functions
Features
func Features(): intFEATURE_HW_CRYPTO together with FEATURE_X86 or FEATURE_ARM64. It is 0 when the backend does not lower the instructions or the processor lacks them.The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwAesEnc
func HwAesEnc(state: u128, key: u128): u128aesenc of x86 (SubBytes, ShiftRows, MixColumns, then xor with the round key). The block is the 16 bytes in memory order, read as a little endian u128.The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwAesEncLast
func HwAesEncLast(state: u128, key: u128): u128aesenclast of x86.The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwClmul
func HwClmul(a: u128, b: u128, imm: int): u128a and one of b, as the 128-bit result. Bit 0 of imm picks the half of a and bit 4 the half of b, as in pclmulqdq. It must be an integer literal.The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwSwap64
func HwSwap64(x: u128): u128The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwBswap
func HwBswap(x: u128): u128The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwLoad
func HwLoad(p: void*): u128The example for this item is shown with the ones above: cpu-Intrinsics.salam
HwStore
func HwStore(p: void*, x: u128)The example for this item is shown with the ones above: cpu-Intrinsics.salam
SetHardware
func SetHardware(enable: bool)The example for this item is shown with the ones above: cpu-Intrinsics.salam
HasHardwareCrypto
func HasHardwareCrypto(): boolThe example for this item is shown with the ones above: cpu-Intrinsics.salam
HasAesNi
func HasAesNi(): boolThe example for this item is shown with the ones above: cpu-Intrinsics.salam
HasPclmul
func HasPclmul(): boolHasAesNi.The example for this item is shown with the ones above: cpu-Intrinsics.salam
HasArmCrypto
func HasArmCrypto(): boolThe example for this item is shown with the ones above: cpu-Intrinsics.salam