HNHacker News
TopNewBestAskShowJobs

pbsd

2,790 karma · joined November 14, 2011

submissionscomments
pbsd··on An Almost-Secret Algorithm Researchers Used to Break Thousands of RSA Keys
Isn't the point of "Prime and Prejudice" that primality tests that are perfectly adequate for RSA key generation are inadequate to verify the primality of adversarial inputs, in protocols like negotiated DHE or whatnot?
pbsd··on Mathematicians Seal Loophole to Breaking RSA Encryption
The first step in the number field sieve is to represent the number to be factored as a polynomial (two, in fact), and it is assumed in the following steps that these polynomials do not split. If these polynomials did split with reasonable probability, the number field sieve would be a lot shorter.

The usual reference for this is Brilhart et al. [1], which deals with polynomial representations in arbitrary bases, as opposed to strictly base 2, as is the case here.

[1] https://doi.org/10.4153/CJM-1981-080-0

pbsd··on Using Fourier Transforms to Multiply Numbers
You're essentially describing a residue number system, except the different moduli need to be coprime for things to be well-defined.

FFT multiplication is basically a residue number system product on polynomials. Evaluating a polynomial p(x) at a point w_i is the same as computing p(x) modulo (x - w_i), where w_i here is one of the nth roots of unity. So the FFT computes p(x) mod (x - w_0), p(x) mod (x - w_1), ..., multiplies each residue individually, and the inverse FFT recovers the result modulo (x - w_0)(x - w_1)...(x - w_{n-1}) = x^n - 1.

pbsd··on Crypto 101 – Introductory course on cryptography
Most of the eSTREAM finalists (http://www.ecrypt.eu.org/stream/index.html) are not block-based. I think only Salsa20 matches that description.

More generally, I think the distinction between a "block-based stream cipher" and a block cipher (or permutation) mode is the degree to which you abstract away the block cipher. Personally I wouldn't put them together. Counter mode completely abstracts away the cipher, assuming it is an idealized pseudorandom function; sponge-based modes also abstract away the permutation as ideal in most cases; stream ciphers usually do not do this, and use weaker (but still sometimes block-based) primitives to generate output. There are, for example, sponge modes that use very few rounds past the initialization, which makes them more streamy than spongy. So their cryptanalysis is often different.

pbsd··on AMD’s aggressive pricing update on the EPYC 7371
CNL also added another AES unit, so you can now dispatch aesenc and its ilk to ports 0 and 1.
pbsd··on The OCB2 authenticated encryption scheme (ISO standard) has been broken
OCB2 is not a block cipher; it is an authenticated encryption scheme built on top of one.

The scary thing here is not so much the error in the proof, which does not have many repercussions beyond OCB2, but that it went 14 years without being discovered. This despite the OCB2 paper, which introduced XEX, being highly influential. Every time something like this happens, confidence on the security of all "provably secure" schemes is undermined.

pbsd··on Towards fearless SIMD
There is the case of LZCNT and BSR. On processors that do not support the former, LZCNT is interpreted as (REP) BSR, but the instructions have different behavior that could result in silent failures.
pbsd··on PCG: a family of better random number generators
I will continue to mark the security of PCG as "unwilling to quantify", which is strictly below "better than nothing". Debating the virtues of the latter is therefore pointless.
pbsd··on PCG: a family of better random number generators
"Not breakable in fewer than O(2^(n/2)) operations" is an imprecise security claim; "challenging" means nothing. "Challenging" may not even mean "computationally hard"---breaking a truncated LCG is arguably quite challenging if you are unfamiliar with lattice reduction.
pbsd··on PCG: a family of better random number generators
"Challenging" is inherently unfalsifiable, and gives you very little information about what security you're getting. You can come up with a better-than-bruteforce attack, only to be met with "see? I was right all along, it was still challenging!". There's a reason cryptographic algorithms come with concrete security claims; it becomes a mess of definitions and rationalizing of attacks otherwise.
pbsd··on PCG: a family of better random number generators
An AEAD is consuming n bytes of input per cycle and producing at least n bytes of output, that is, the output ciphertext plus tag.

The definition of authenticated encryption is, essentially, indistinguishability of the ciphertext plus MAC security of the tag. You're right, the tag does not need to be indistinguishable from random, but in the majority of the cases it is anyway.

One significant difference between these non-cryptographic generators and cryptographic ciphers is that of latency---generators often optimize for latency, whereas ciphers optimize for throughput. By taking a cipher and a sufficiently large buffer, you can have low latency as well, at the cost of some memory.

For comparison, consider MORUS, AEGIS, and Tiaoxin, three unbroken contestants of the CAESAR competition. MORUS uses only AND, XOR, and bitwise rotation, and achieves somewhere between 0.5 and 0.66 cycles per byte on current x86 chips. AEGIS and Tiaoxin use the AES round, and where AES-NI is available, performs at between 0.15 to 0.25 cycles per byte. The claim somewhere above is that PCG can do 0.25 cycles per byte (or 4 words per cycle) when going all out with AVX-512; that's only hard to beat if you've not been paying attention.

pbsd··on How Rust 1.26 more than tripled the speed of my code
Agner's instruction listings [1] and InstLatX64 [2] are good sources for a variety of chips. The 24 cycle figure above can be seen at https://github.com/InstLatx64/InstLatx64/blob/ee13abcfb1e2bb...

[1] http://www.agner.org/optimize/#manuals

[2] https://github.com/InstLatx64

pbsd··on How Rust 1.26 more than tripled the speed of my code
It's still doing it, but now it uses `lahf` + `sahf` instead to (re)store the flags. These are better than pushf + popf for sure, but they cannot be used in general code because some early x86_64 chips forgot to implement them.
pbsd··on How Rust 1.26 more than tripled the speed of my code
Where are you getting that from? `pushfd` is OK-ish, but `popfd` is microcoded, requires 9 uops, and can only be issued once every ~20 cycles. That's not what I would call well-pipelined.

You could probably achieve the same result (assuming you want to avoid adc instructions, for some reason) by using setc r8 plus shr r8, 1 to get the carry back.

pbsd··on How Rust 1.26 more than tripled the speed of my code
That is not the case. On a Skylake chip, a pushfd + popfd roundtrip costs 24 cycles.
pbsd··on How Rust 1.26 more than tripled the speed of my code
The code in the blog post does not match what is actually benchmarked. The reason `u256_full_mul` is so much faster than the inline assembly version is that it omits the upper part of the result, due to how the benchmark is done (cf. https://github.com/paritytech/bigint/blob/44a3133030306d9fcc...). There should be 16 full 64x64->128 multiplications (unless you're doing Karatsuba, which is not the case here) in a full 256-bit multiplication, but I only count 6 in the benchmark's inner loop (plus 4 64x64->64 multiplications). No wonder it's faster.

The disassembly for the Rust output of `u256_full_mul` at the end is also terrible, and it looks like the output of a debug build. There's no reason an optimizing compiler should be outputting `pushfd` and `popfd` in performant code. The benchmarked loop does not have those instructions.

pbsd··on Cryptographic Right Answers
To be pedantic, Keccak does have a 5 to 5 bit S-box. But like 3-Way, NOEKEON, Serpent, etc, it's disguised---bitsliced---as a short sequence of boolean operations.
pbsd··on Micro-op fusion in x86
This theory is cool, but I don't think it works, all things considered. PDEP and PEXT should have the same unfused behavior as SHLX, since they also do not change any flags, but they _do_ fuse. BEXTR should (or could) fuse, but doesn't. So I don't know.
pbsd··on Micro-op fusion in x86
Renaming is unrelated to my guess about the flags. The point is that there's a limit to how many inputs a fused uop can have, 3, and the flags register may become one input too many to be able to fuse the uops. For example,

    inc [rdi+rbx]
has the obvious rdi and rbx dependencies, the flags register, plus (presumably, depending on implementation details) an allocated virtual register for the 1 that is added. On the register forms this limit is never a bottleneck.

You also see the same behavior, according to Agner, on SHR/SHL m, i, which may or may not alter some flags depending on shift amounts, and strangely on NOT m, which explicitly does not alter the flags in any situation. This latter one makes little sense.

pbsd··on Micro-op fusion in x86
According to Agner when the uops go back to to the reorder buffer to get retired they are still treated as fused. However, if you look at the descriptions of the events [1, 2] a retired fused uop is counted as 2 retired uops.

And yes, if you do mov r, m / add r, 1 / mov m, r instead of add m, 1 no such fusion happens. This is specific to CISCy instructions that do more than one thing at the same time.

[1] https://software.intel.com/sites/products/documentation/docl...

[2] https://software.intel.com/sites/products/documentation/docl...

pbsd··on Micro-op fusion in x86
As far as I understand, with micro uop fusion the number of retired uops should be higher than the issued uops.
pbsd··on Micro-op fusion in x86
> The point is that the three instruction format and the single instruction format you would think would decode both to three micro-ops (or something like that), but instead the single instruction format can use a single fused micro op. That's why it's better to write as a single instruction.

This is not exactly what happens. A read-modify-write instruction in general results in 2 fused uops, one for the read+op, the other for the address generation+write.

In the specific case of INC, you still have 3 fused uops for unclear reasons---possibly because INC only partially changes the flags. Replacing the expression by ADD [memory], 1 results on the expected 2 fused ops.

pbsd··on Some thoughts on Spectre and Meltdown
That seems fine on its own, but it somewhat undermines the paragraph with "but they have all followed the same basic mechanism", seeing as these earlier attacks relying on caches etc did not follow this mechanism. Changing "all" to "most" or "many" would probably suffice.
pbsd··on Some thoughts on Spectre and Meltdown
That's a much more defensible claim---one I have no issue with---but it was easy to misread the post and believe it was claiming something more general.
pbsd··on Some thoughts on Spectre and Meltdown
I'm gonna go and take issue with the claim that you were the first to come up with microarchitectural attacks, and that their story begins in 2004:

- Dan Page published [1] in 2002, describing an attack on DES exploiting cache timings. In 2003, he published [2] describing countermeasures to this class of attacks.

- Concurrently, a Japanese team also attacked DES (and MISTY1) with cache timing [3, 4].

- Dan Bernstein published the first version of his AES cache timing attack in late 2004 [5].

[1] https://eprint.iacr.org/2002/169

[2] https://doi.org/10.1016/S1363-4127(03)00104-3

[3] https://link.springer.com/chapter/10.1007/978-3-540-45238-6_...

[4] https://web.archive.org/web/20060906064630/http://web.engr.o...

[5] https://cr.yp.to/antiforgery/cachetiming-20041121.pdf

pbsd··on Rust vector guarantees
C++ is not allowed to do the small buffer optimization for std::vector<T>. You'll find a number of small_vector<T> classes from third parties, though.

The reason is that std::swap(v1, v2) must never invalidate existing iterators and/or references to elements into either vector, which the optimization would clearly break.

std::string is the type you may be thinking of, where the standard does make a special exception to enable the SSO.

pbsd··on Intel i7 loop performance anomaly (2013)
I later realized that padding to 32 bytes didn't make much of a difference, it is the number of vacuous NOP opcodes generated that matters. So it can be simplified to

    mov rcx, 400000000-1
    up:
    nop
    nop
    nop
    add [counter], rcx
    nop
    nop
    nop
    dec rcx
    nop
    nop
    nop
    jnz up
    ret
which probably works fine for the majority of decoding methods, be it MITE, LSD, or DSB.
pbsd··on Intel i7 loop performance anomaly (2013)
> I presume you are seeing the instructions showing up as IDQ_MITE_UOPS?

No, the legacy decoder is virtually unused. This loop was designed to take advantage of the rules of the DSB. If you have the LSD enabled I don't quite know what the effect is.

EDIT: Sorry, just realized I screwed up translating AT&T to Intel---add rcx, [counter] should clearly be add [counter], rcx.

> It may be that the maximum difference is whether we get a 4 cycle turnaround on the store-load forwarding versus a 5 cycle.

This is my guess as well, though I have nothing to show for it. It's actually possible to bring it down to 400000000 uops by delaying the loop even further with the same technique, but it won't speed anything up. The 4-cycles-per-iteration barrier seems insurmountable, which makes sense.

pbsd··on Intel i7 loop performance anomaly (2013)
We are observing different effects. I suggest you apply the latest microcode update, seeing that you have LSD_UOPS being dispatched; the latest update disabled the LSD altogether, due to the SKL150 CPU errata. The LSD was also permanently disabled in the newer Skylake-X, probably for the same reason.

In any case, I have to admit being wrong; port 6 has nothing to do with it, though the general principle of stalling the frontend to prevent the backend being overloaded still holds. For example, here's how to get the speedup without any change in control flow:

    mov rcx, 400000000-1
    align 32
    nop
    up:
    align 32
    add rcx, [counter]
    align 32
    dec rcx
    align 32
    jnz up
    ret
The align directives here must resolve to an appropriate series of multi-byte nops. What we are doing here is to purposefully waste the uops cache. Each useful instruction comes at the beginning of a new 32-byte uop cache line, and the CPU cannot load more than 1 cache line per cycle. Effectively, we're limiting the rate of decoding by adding nops, which will not result in actual uops after decoding. I suspect the CALL and JMP instructions in the other variants were doing the same thing, but in a more obscure way. It's possible that CALL disables the LSD, which would matter in Sandy Bridge, but not in (updated) Skylake.

As to why this actually makes things faster, I don't know. There are many more stalls owing to the reservation station being full in the tight loop:

     1,348,491,506      resource_stalls_any                                         
     1,694,613,980      uops_issued_stall_cycles                                    
       759,763,767      uops_executed_stall_cycles                                   
       994,974,421      uops_retired_stall_cycles    
vs

       109,300,935      resource_stalls_any                                         
       120,303,318      uops_issued_stall_cycles                                    
       164,322,430      uops_executed_stall_cycles                                   
        15,834,456      uops_retired_stall_cycles  
But how does this translate to the observed performance penalty? One clue lies in the port breakdown:

       756,686,062      uops_dispatched_port_port_0
       782,579,771      uops_dispatched_port_port_1
       279,387,822      uops_dispatched_port_port_2
       314,214,218      uops_dispatched_port_port_3
     1,994,313,938      uops_dispatched_port_port_4
       807,514,909      uops_dispatched_port_port_5
     1,050,884,205      uops_dispatched_port_port_6
       209,316,457      uops_dispatched_port_port_7
vs

       640,957,968      uops_dispatched_port_port_0
       637,621,266      uops_dispatched_port_port_1
       399,224,647      uops_dispatched_port_port_2
       400,205,411      uops_dispatched_port_port_3
     1,610,294,604      uops_dispatched_port_port_4
       643,780,748      uops_dispatched_port_port_5
       848,142,785      uops_dispatched_port_port_6
           470,700      uops_dispatched_port_port_7

Focus on port 4, the only port available for memory stores. It does nothing else, so ideally we would expect ~400000000 uops to be dispatched to port 4. Instead, we have ~4x that for the fast loop, and ~5x that for the slow loop. These, presumably, are speculative stores that had to be invalidated. By dispatching fewer instructions that we can handle per cycle, there is less speculation and thus less wasted work?
pbsd··on Intel i7 loop performance anomaly (2013)
The call shifts the loop from being limited at the backend level (mostly uops not being retired by having to wait for memory) to being limited at the frontend level. The core event to look for here is `IDQ_UOPS_NOT_DELIVERED.CORE`, which tells us whether the frontend is delivering the full 4 uops per cycle it is able to. In the tight loop this is almost always the case, whereas in the call loop this is rarely the case.

CALL, RET, and JNE all share the same execution port (6 in Skylake), so it seems plausible that the added pressure in this port prevents speculative execution from continuing with the loop at the same rate as the tight loop. If you look at the execution port breakdowns of each loop, port 6 dominates in the call loop, whereas the tight loop is bottlenecked at port 4 (the port where stores go).

By delivering fewer uops per cycle, the pressure on the backend is eased. But this is a delicate balance. If you add another call, the loop becomes much slower than the tight loop.

You can get a similar effect by replacing `__asm__("call foo")` with

    __asm__("jmp 1f\n1:\n");
    __asm__("jmp 1f\n1:\n");
which consumes the same amount of port 6.
← PreviousPage 5 of 26Next →