From 5b1b7764068d5cb17e4b888d1c7862328797dd4f Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 11 Jul 2020 21:07:31 -0500 Subject: [PATCH] secp256k1: Harden const time field normalization. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This updates the field normalization code to better secure against the possibility of non-constant time operations due to branch prediction and adds several tests to ensure the new logic is sound. The following benchmark results show that this implementation is within the margin of error for it to not be statistically relevant and thus has no performance impact. name old time/op new time/op delta ---------------------------------------------------------------------- FieldNormalize 22.1ns ± 1% 22.1ns ± 1% ~ (p=0.873 n=5+5) --- dcrec/secp256k1/field.go | 32 +++----------- dcrec/secp256k1/field_bench_test.go | 7 ++- dcrec/secp256k1/field_test.go | 67 +++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/dcrec/secp256k1/field.go b/dcrec/secp256k1/field.go index 9c633a08d9..1b7809ccee 100644 --- a/dcrec/secp256k1/field.go +++ b/dcrec/secp256k1/field.go @@ -391,32 +391,12 @@ func (f *FieldVal) Normalize() *FieldVal { // following determines if either or these conditions are true and does // the final reduction in constant time. // - // Note that the if/else statements here intentionally do the bitwise - // operators even when it won't change the value to ensure constant time - // between the branches. Also note that 'm' will be zero when neither - // of the aforementioned conditions are true and the value will not be - // changed when 'm' is zero. - m = 1 - if t9 == fieldMSBMask { - m &= 1 - } else { - m &= 0 - } - if t2&t3&t4&t5&t6&t7&t8 == fieldBaseMask { - m &= 1 - } else { - m &= 0 - } - if ((t0+977)>>fieldBase + t1 + 64) > fieldBaseMask { - m &= 1 - } else { - m &= 0 - } - if t9>>fieldMSBBits != 0 { - m |= 1 - } else { - m |= 0 - } + // Also note that 'm' will be zero when neither of the aforementioned + // conditions are true and the value will not be changed when 'm' is zero. + m = constantTimeEq(t9, fieldMSBMask) + m &= constantTimeEq(t8&t7&t6&t5&t4&t3&t2, fieldBaseMask) + m &= constantTimeGreater(t1+64+((t0+977)>>fieldBase), fieldBaseMask) + m |= t9 >> fieldMSBBits t0 = t0 + m*977 t1 = (t0 >> fieldBase) + t1 + (m << 6) t0 = t0 & fieldBaseMask diff --git a/dcrec/secp256k1/field_bench_test.go b/dcrec/secp256k1/field_bench_test.go index c96e1efaae..e7937ba646 100644 --- a/dcrec/secp256k1/field_bench_test.go +++ b/dcrec/secp256k1/field_bench_test.go @@ -12,8 +12,11 @@ import ( // BenchmarkFieldNormalize benchmarks how long it takes the internal field // to perform normalization (which includes modular reduction). func BenchmarkFieldNormalize(b *testing.B) { - // The normalize function is constant time so default value is fine. - f := new(FieldVal) + // The function is constant time so any value is fine. + f := &FieldVal{n: [10]uint32{ + 0x000148f6, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, + 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x00000007, + }} for i := 0; i < b.N; i++ { f.Normalize() } diff --git a/dcrec/secp256k1/field_test.go b/dcrec/secp256k1/field_test.go index 5abffb2866..b144051b5d 100644 --- a/dcrec/secp256k1/field_test.go +++ b/dcrec/secp256k1/field_test.go @@ -769,6 +769,73 @@ func TestFieldNormalize(t *testing.T) { name: "Value > P with redux > P at mag 1 due to 1st and 2nd words and carry to bit 256", raw: [10]uint32{0x03fffc30, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x07ffffff, 0x003fffff}, normalized: [10]uint32{0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001}, + }, { + // --------------------------------------------------------------------- + // There are 3 main conditions that must be true if the final reduction + // is needed after the initial reduction to magnitude 1 when there was + // NOT a carry to bit 256 (in other words when the original value was < + // 2^256): + // 1) The final word of the reduced value is equal to the one of P + // 2) The 3rd through 9th words are equal to those of P + // 3) Either: + // - The 2nd word is greater than the one of P; or + // - The 2nd word is equal to that of P AND the 1st word is greater + // + // Therefore the eight possible combinations of those 3 main conditions + // can be thought of in binary where each bit starting from the left + // corresponds to the aforementioned conditions as such: + // 000, 001, 010, 011, 100, 101, 110, 111 + // + // For example, combination 6 is when both conditons 1 and 2 are true, + // but condition 3 is NOT true. + // + // The following tests hit each of these combinations and refer to each + // by its decimal equivalent for ease of reference. + // + // NOTE: The final combination (7) is already tested above since it only + // happens when the original value is already the normalized + // representation of P. + // --------------------------------------------------------------------- + + name: "Value < 2^256 final reduction combination 0", + raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe}, + normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe}, + }, { + name: "Value < 2^256 final reduction combination 1 via 2nd word", + raw: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe}, + normalized: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe}, + }, { + name: "Value < 2^256 final reduction combination 1 via 1st word", + raw: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe}, + normalized: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe}, + }, { + name: "Value < 2^256 final reduction combination 2", + raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe}, + normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe}, + }, { + name: "Value < 2^256 final reduction combination 3 via 2nd word", + raw: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe}, + normalized: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe}, + }, { + name: "Value < 2^256 final reduction combination 3 via 1st word", + raw: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe}, + normalized: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe}, + }, { + name: "Value < 2^256 final reduction combination 4", + raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff}, + normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff}, + }, { + name: "Value < 2^256 final reduction combination 5 via 2nd word", + raw: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff}, + normalized: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff}, + }, { + name: "Value < 2^256 final reduction combination 5 via 1st word", + raw: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff}, + normalized: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff}, + }, { + name: "Value < 2^256 final reduction combination 6", + raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003fffff}, + normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003fffff}, }} for _, test := range tests {