Skip to content

Commit 77ac77d

Browse files
committed
secp256k1: Harden const time field normalization.
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. Benchmarking results show that this implementation is within the margin of error and thus has no performance impact.
1 parent 5616620 commit 77ac77d

3 files changed

Lines changed: 78 additions & 28 deletions

File tree

dcrec/secp256k1/field.go

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -391,32 +391,12 @@ func (f *FieldVal) Normalize() *FieldVal {
391391
// following determines if either or these conditions are true and does
392392
// the final reduction in constant time.
393393
//
394-
// Note that the if/else statements here intentionally do the bitwise
395-
// operators even when it won't change the value to ensure constant time
396-
// between the branches. Also note that 'm' will be zero when neither
397-
// of the aforementioned conditions are true and the value will not be
398-
// changed when 'm' is zero.
399-
m = 1
400-
if t9 == fieldMSBMask {
401-
m &= 1
402-
} else {
403-
m &= 0
404-
}
405-
if t2&t3&t4&t5&t6&t7&t8 == fieldBaseMask {
406-
m &= 1
407-
} else {
408-
m &= 0
409-
}
410-
if ((t0+977)>>fieldBase + t1 + 64) > fieldBaseMask {
411-
m &= 1
412-
} else {
413-
m &= 0
414-
}
415-
if t9>>fieldMSBBits != 0 {
416-
m |= 1
417-
} else {
418-
m |= 0
419-
}
394+
// Also note that 'm' will be zero when neither of the aforementioned
395+
// conditions are true and the value will not be changed when 'm' is zero.
396+
m = constantTimeEq(t9, fieldMSBMask)
397+
m &= constantTimeEq(t8&t7&t6&t5&t4&t3&t2, fieldBaseMask)
398+
m &= constantTimeGreater(t1+64+((t0+977)>>fieldBase), fieldBaseMask)
399+
m |= t9 >> fieldMSBBits
420400
t0 = t0 + m*977
421401
t1 = (t0 >> fieldBase) + t1 + (m << 6)
422402
t0 = t0 & fieldBaseMask

dcrec/secp256k1/field_bench_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import (
1212
// BenchmarkFieldNormalize benchmarks how long it takes the internal field
1313
// to perform normalization (which includes modular reduction).
1414
func BenchmarkFieldNormalize(b *testing.B) {
15-
// The normalize function is constant time so default value is fine.
16-
f := new(FieldVal)
15+
// The function is constant time so any value is fine.
16+
f := &FieldVal{n: [10]uint32{
17+
0x000148f6, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff,
18+
0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x00000007,
19+
}}
1720
for i := 0; i < b.N; i++ {
1821
f.Normalize()
1922
}

dcrec/secp256k1/field_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,73 @@ func TestFieldNormalize(t *testing.T) {
769769
name: "Value > P with redux > P at mag 1 due to 1st and 2nd words and carry to bit 256",
770770
raw: [10]uint32{0x03fffc30, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x07ffffff, 0x003fffff},
771771
normalized: [10]uint32{0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001},
772+
}, {
773+
// ---------------------------------------------------------------------
774+
// There are 3 main conditions that must be true if the final reduction
775+
// is needed after the initial reduction to magnitude 1 when there was
776+
// NOT a carry to bit 256 (in other words when the original value was <=
777+
// P):
778+
// 1) The final word of the reduced value is equal to the one of P
779+
// 2) The 3rd through 9th words are equal to those of P
780+
// 3) Either:
781+
// - The 2nd word is greater than the one of P; or
782+
// - The 2nd word is equal to that of P AND the 1st word is greater
783+
//
784+
// Therefore the eight possible combinations of those 3 main conditions
785+
// can be thought of in binary where each bit starting from the left
786+
// corresponds to the aforementioned conditions as such:
787+
// 000, 001, 010, 011, 100, 101, 110, 111
788+
//
789+
// For example, combination 6 is when both conditons 1 and 2 are true,
790+
// but condition 3 is NOT true.
791+
//
792+
// The following tests hit each of these combinations and refer to each
793+
// by its decimal equivalent for ease of reference.
794+
//
795+
// NOTE: The final combination (7) is already tested above since it only
796+
// happens when the original value is already the normalized
797+
// representation of P.
798+
// ---------------------------------------------------------------------
799+
800+
name: "Value <= P final reduction combination 0",
801+
raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe},
802+
normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe},
803+
}, {
804+
name: "Value <= P final reduction combination 1 via 2nd word",
805+
raw: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe},
806+
normalized: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe},
807+
}, {
808+
name: "Value <= P final reduction combination 1 via 1st word",
809+
raw: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe},
810+
normalized: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003ffffe},
811+
}, {
812+
name: "Value <= P final reduction combination 2",
813+
raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe},
814+
normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe},
815+
}, {
816+
name: "Value <= P final reduction combination 3 via 2nd word",
817+
raw: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe},
818+
normalized: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe},
819+
}, {
820+
name: "Value <= P final reduction combination 3 via 1st word",
821+
raw: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe},
822+
normalized: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003ffffe},
823+
}, {
824+
name: "Value <= P final reduction combination 4",
825+
raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff},
826+
normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff},
827+
}, {
828+
name: "Value <= P final reduction combination 5 via 2nd word",
829+
raw: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff},
830+
normalized: [10]uint32{0x03fff85e, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff},
831+
}, {
832+
name: "Value <= P final reduction combination 5 via 1st word",
833+
raw: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff},
834+
normalized: [10]uint32{0x03fffc2f, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03fffffe, 0x003fffff},
835+
}, {
836+
name: "Value <= P final reduction combination 6",
837+
raw: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003fffff},
838+
normalized: [10]uint32{0x03fff85e, 0x03ffffbf, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x003fffff},
772839
}}
773840

774841
for _, test := range tests {

0 commit comments

Comments
 (0)