Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

secp256k1: Add field func to determine when >= P-N. #2093

Merged
merged 1 commit into from
Feb 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions dcrec/secp256k1/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -1394,3 +1394,70 @@ func (f *fieldVal) Inverse() *fieldVal {
f.Square().Square().Square().Square().Square() // f = a^(2^256 - 4294968320)
return f.Mul(&a45) // f = a^(2^256 - 4294968275) = a^(p-2)
}

// IsGtOrEqPrimeMinusOrder returns whether or not the scalar exceeds the group order
// divided by 2 in constant time.
//
// The field value must be normalized for this function to return the correct
// result.
func (f *fieldVal) IsGtOrEqPrimeMinusOrder() bool {
// The secp256k1 prime is equivalent to 2^256 - 4294968273 and the group
// order is 2^256 - 432420386565659656852420866394968145599. Thus,
// the prime minus the group order is:
// 432420386565659656852420866390673177326
//
// In hex that is:
// 0x00000000 00000000 00000000 00000001 45512319 50b75fc4 402da172 2fc9baee
//
// Converting that to field representation (base 2^26) is:
//
// n[0] = 0x03c9baee
// n[1] = 0x03685c8b
// n[2] = 0x01fc4402
// n[3] = 0x006542dd
// n[4] = 0x01455123
//
// This can be verified with the following test code:
// pMinusN := new(big.Int).Sub(curveParams.P, curveParams.N)
// fv := new(fieldVal).SetByteSlice(pMinusN.Bytes())
// t.Logf("%x", fv.n)
//
// Outputs: [3c9baee 3685c8b 1fc4402 6542dd 1455123 0 0 0 0 0]
const (
pMinusNWordZero = 0x03c9baee
pMinusNWordOne = 0x03685c8b
pMinusNWordTwo = 0x01fc4402
pMinusNWordThree = 0x006542dd
pMinusNWordFour = 0x01455123
pMinusNWordFive = 0x00000000
pMinusNWordSix = 0x00000000
pMinusNWordSeven = 0x00000000
pMinusNWordEight = 0x00000000
pMinusNWordNine = 0x00000000
)

// The intuition here is that the value is greater than field prime minus
// the group order if one of the higher individual words is greater than the
// corresponding word and all higher words in the value are equal.
result := constantTimeGreater(f.n[9], pMinusNWordNine)
highWordsEqual := constantTimeEq(f.n[9], pMinusNWordNine)
result |= highWordsEqual & constantTimeGreater(f.n[8], pMinusNWordEight)
highWordsEqual &= constantTimeEq(f.n[8], pMinusNWordEight)
result |= highWordsEqual & constantTimeGreater(f.n[7], pMinusNWordSeven)
highWordsEqual &= constantTimeEq(f.n[7], pMinusNWordSeven)
result |= highWordsEqual & constantTimeGreater(f.n[6], pMinusNWordSix)
highWordsEqual &= constantTimeEq(f.n[6], pMinusNWordSix)
result |= highWordsEqual & constantTimeGreater(f.n[5], pMinusNWordFive)
highWordsEqual &= constantTimeEq(f.n[5], pMinusNWordFive)
result |= highWordsEqual & constantTimeGreater(f.n[4], pMinusNWordFour)
highWordsEqual &= constantTimeEq(f.n[4], pMinusNWordFour)
result |= highWordsEqual & constantTimeGreater(f.n[3], pMinusNWordThree)
highWordsEqual &= constantTimeEq(f.n[3], pMinusNWordThree)
result |= highWordsEqual & constantTimeGreater(f.n[2], pMinusNWordTwo)
highWordsEqual &= constantTimeEq(f.n[2], pMinusNWordTwo)
result |= highWordsEqual & constantTimeGreater(f.n[1], pMinusNWordOne)
highWordsEqual &= constantTimeEq(f.n[1], pMinusNWordOne)
result |= highWordsEqual & constantTimeGreaterOrEq(f.n[0], pMinusNWordZero)

return result != 0
}
37 changes: 37 additions & 0 deletions dcrec/secp256k1/field_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,40 @@ func BenchmarkBigSqrt(b *testing.B) {
_ = new(big.Int).ModSqrt(val, curveParams.P)
}
}

// BenchmarkFieldIsGtOrEqPrimeMinusOrder benchmarks determining whether a value
// is greater than or equal to the field prime minus the group order with the
// specialized type.
func BenchmarkFieldIsGtOrEqPrimeMinusOrder(b *testing.B) {
// The function is constant time so any value is fine.
valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca"
f := new(fieldVal).SetHex(valHex).Normalize()

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = f.IsGtOrEqPrimeMinusOrder()
}
}

// BenchmarkBigIsGtOrEqPrimeMinusOrder benchmarks determining whether a value
// is greater than or equal to the field prime minus the group order with stdlib
// big integers.
func BenchmarkBigIsGtOrEqPrimeMinusOrder(b *testing.B) {
// Same value used in field val version.
valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca"
val, ok := new(big.Int).SetString(valHex, 16)
if !ok {
b.Fatalf("failed to parse hex %s", valHex)
}
bigPMinusN := new(big.Int).Sub(curveParams.P, curveParams.N)

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// In practice, the internal value to compare would have to be converted
// to a big integer from bytes, so it's a fair comparison to allocate a
// new big int here and set all bytes.
_ = new(big.Int).SetBytes(val.Bytes()).Cmp(bigPMinusN) >= 0
}
}
112 changes: 112 additions & 0 deletions dcrec/secp256k1/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1082,3 +1082,115 @@ func TestInverse(t *testing.T) {
}
}
}

// TestFieldIsGtOrEqPrimeMinusOrder ensures that field values report whether or
// not they are greater than or equal to the field prime minus the group order
// as expected for edge cases.
func TestFieldIsGtOrEqPrimeMinusOrder(t *testing.T) {
tests := []struct {
name string // test description
in string // hex encoded test value
expected bool // expected result
}{{
name: "zero",
in: "0",
expected: false,
}, {
name: "one",
in: "1",
expected: false,
}, {
name: "p - n - 1",
in: "14551231950b75fc4402da1722fc9baed",
expected: false,
}, {
name: "p - n",
in: "14551231950b75fc4402da1722fc9baee",
expected: true,
}, {
name: "p - n + 1",
in: "14551231950b75fc4402da1722fc9baef",
expected: true,
}, {
name: "over p - n word one",
in: "14551231950b75fc4402da17233c9baee",
expected: true,
}, {
name: "over p - n word two",
in: "14551231950b75fc4403da1722fc9baee",
expected: true,
}, {
name: "over p - n word three",
in: "14551231950b79fc4402da1722fc9baee",
expected: true,
}, {
name: "over p - n word four",
in: "14551241950b75fc4402da1722fc9baee",
expected: true,
}, {
name: "over p - n word five",
in: "54551231950b75fc4402da1722fc9baee",
expected: true,
}, {
name: "over p - n word six",
in: "100000014551231950b75fc4402da1722fc9baee",
expected: true,
}, {
name: "over p - n word seven",
in: "000000000000000000400000000000014551231950b75fc4402da1722fc9baee",
expected: true,
}, {
name: "over p - n word eight",
in: "000000000001000000000000000000014551231950b75fc4402da1722fc9baee",
expected: true,
}, {
name: "over p - n word nine",
in: "000004000000000000000000000000014551231950b75fc4402da1722fc9baee",
expected: true,
}}

for _, test := range tests {
result := new(fieldVal).SetHex(test.in).IsGtOrEqPrimeMinusOrder()
if result != test.expected {
t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name,
result, test.expected)
continue
}
}
}

// TestFieldIsGtOrEqPrimeMinusOrderRandom ensures that field values report
// whether or not they are greater than or equal to the field prime minus the
// group order as expected by also performing the same operation with big ints
// and comparing the results.
func TestFieldIsGtOrEqPrimeMinusOrderRandom(t *testing.T) {
// Use a unique random seed each test instance and log it if the tests fail.
seed := time.Now().Unix()
rng := rand.New(rand.NewSource(seed))
defer func(t *testing.T, seed int64) {
if t.Failed() {
t.Logf("random seed: %d", seed)
}
}(t, seed)

bigPMinusN := new(big.Int).Sub(curveParams.P, curveParams.N)
for i := 0; i < 100; i++ {
// Generate big integer and field value with the same random value.
bigIntVal, fVal := randIntAndFieldVal(t, rng)

// Determine the value is greater than or equal to the prime minus the
// order using big ints.
bigIntResult := bigIntVal.Cmp(bigPMinusN) >= 0

// Determine the value is greater than or equal to the prime minus the
// order using fieldVal.
fValResult := fVal.IsGtOrEqPrimeMinusOrder()

// Ensure they match.
if bigIntResult != fValResult {
t.Fatalf("mismatched is gt or eq prime minus order\nbig int in: "+
"%x\nscalar in: %v\nbig int result: %v\nscalar result %v",
bigIntVal, fVal, bigIntResult, fValResult)
}
}
}