According to OpenID Authentication 2.0, section 4.2,
Arbitrary precision integers MUST be encoded as big-endian signed two’s complement binary strings. Henceforth, "btwoc" is a function that takes an arbitrary precision integer and returns its shortest big-endian two’s complement representation. All integers that are used with Diffie-Hellman Key Exchange are positive. This means that the left-most bit of the two’s complement representation MUST be zero. If it is not, implementations MUST add a zero byte at the front of the string.
Non-normative example:
Base 10 number | btwoc string representation
---------------+----------------------------
0 | "\x00"
127 | "\x7F"
128 | "\x00\x80"
255 | "\x00\xFF"
32768 | "\x00\x80\x00"
I have tried writing my own implementation of btwoc in C, and this is what I have:
typedef struct {
uint8_t *data;
uintmax_t length;
} oid_data;
oid_data *oid_btwoc_r(uintmax_t value, oid_data *ret) {
unsigned fnz = sizeof(uintmax_t) + 1,
i = sizeof(uintmax_t) * 8;
while (--fnz && (!(value >> (i -= 8) & 0xFF)));
/*
If `value' is non-zero, `fnz' now contains the index of the first
non-zero byte in `value', where 1 refers to the least-significant byte.
`fnz' will therefore be in the range [1 .. sizeof(uintmax_t)]. If
`value' is zero, then `fnz' is zero.
*/
if (!value) {
/* The value is zero */
ret->length = 1;
ret->data[0] = 0;
} else if (value >> ((fnz - 1) * 8 + 7)) {
/* The most significant bit of the first non-zero byte is 1 */
ret->length = fnz + 1;
ret->data[0] = 0;
for (i = 1; i <= fnz; i++)
ret->data[1 + fnz - i] =
value >> ((i - 1) * 8);
} else {
/* The most significant bit of the first non-zero byte is 0 */
ret->length = fnz;
for (i = 1; i <= fnz; i++)
ret->data[fnz - i] =
value >> ((i - 1) * 8);
}
return ret;
}
ret->data should point to valid memory of at least sizeof(uintmax_t) + 1 bytes.
It works fine, and I haven’t discovered any bugs in the implementation yet, but can it be optimised?
If you keep zero as a special case, you should surely deal with it before the
whileloop. (Personal bête noire: I dislike(!value)for(value == 0).)I’ve not tried timing this (yet), but this code produces the same answer as yours (at least on the values I tested; see below) and looks simpler to me, not least because it doesn’t double up the code to deal with the case where the high bit is set in the most significant byte:
The code I used to test this against your version was:
Yes; an early version of the code needed the dump function to see what was going wrong!
Timing
I adapted the original
oid_btwoc_r()to a function returningvoid(no finalreturn) and renamed itoid_btwoc_r1(). I then ran a timing test (on a Mac Mini running MacOS X Lion 10.7.1), and got the timing results:Consequently, it appears that the
oid_btwoc_r2()function is about 20% faster than the original – at least on the data tested. Bigger numbers alter the balance in favour ofoid_btwoc_r(), with `oid_btwoc_r1() then being about 20% faster:Since big numbers are probably more likely than small ones in this context,
oid_btwoc_r1()– or the originaloid_btwoc_r()– is arguably the better choice.The test code follows. The uncommented
forloop is the ‘large number’ version which showsoid_btwoc_r1()working faster thanoid_btwoc_r2(); the commented outforloop is the ‘small number’ version which showsoid_btwoc_r2()working faster thanoid_btowc_r1().