Skip to content

Commit

Permalink
Speed up/reduce memory footprint of the NAF function.
Browse files Browse the repository at this point in the history
This uses two byte arrays instead of a large int array used for NAF (Non-Adjascent Form).

This is a significant speedup using BenchmarkNAF, which reports that it's about 2.5-3x faster. It should also incur lower memory restrictions.
  • Loading branch information
jimmysong committed Feb 5, 2015
1 parent 93ff1a5 commit 1183b60
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 77 deletions.
156 changes: 87 additions & 69 deletions btcec.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,65 +673,68 @@ func (curve *KoblitzCurve) moduloReduce(k []byte) []byte {
// The algorithm here is from Guide to Elliptical Cryptography 3.30 (ref above)
// Essentially, this makes it possible to minimize the number of operations
// since the resulting ints returned will be at least 50% 0's.
func NAF(k []byte) []int {

// Flatten out k into its constituent bits.
// 0x57 => [0 0 1 0 1 0 1 1 1]. This is 0x57 in binary but in 9 bits.
// The extra 0 at the front is needed because the size of what we return
// is 1 more than the number of bits k has.
bits := make([]int, len(k)*8+1)
lenBits := len(bits)
for i, byteVal := range k {
for j := 7; j >= 0; j-- {
if byteVal&1 == 1 {
bits[8*i+j+1] = 1
}
byteVal >>= 1
}
}
func NAF(k []byte) ([]byte, []byte) {

// The essence of this algorithm is that whenever we have consecutive 1s
// in the binary, we want to put a -1 in the lowest bit and get a bunch of
// 0s up to the highest bit of consecutive 1s. This is due to this identity:
// 2^n + 2^(n-1) + 2^(n-2) + ... + 2^(n-k) = 2^(n+1) - 2^(n-k)
// The algorithm thus may need to go 1 more bit than the length of the bits
// we actually have, hence bits being 1 bit longer than was necessary.
// We iterate the bits in reverse since we need to start at the lowest bit.
var carry, nextIsOne bool
ret := make([]int, len(k)*8+1)
for i := lenBits - 1; i >= 0; i-- {
bit := bits[i]
nextIsOne = i > 0 && bits[i-1] == 1
if carry {
if bit == 0 {
// We've hit a 0 after some number of 1s.
if nextIsOne {
// We start carrying again since we're starting
// a new sequence of 1s.
ret[i] = -1
// Since we need to know whether adding will cause a carry, we go from
// right-to-left in this addition.
var carry, curIsOne, nextIsOne bool
// these default to zero
retPos := make([]byte, len(k)+1)
retNeg := make([]byte, len(k)+1)
for i := len(k) - 1; i >= 0; i-- {
curByte := k[i]
for j := uint(0); j < 8; j++ {
curIsOne = curByte&1 == 1
if j == 7 {
if i == 0 {
nextIsOne = false
} else {
// We stop carrying since 1s have stopped.
carry = false
ret[i] = 1
nextIsOne = k[i-1]&1 == 1
}
} else {
// This bit is 1, so we continue to carry and
// don't need to do anything
nextIsOne = curByte&2 == 2
}
} else if bit == 1 {
if nextIsOne {
// if this is the start of at least 2 consecutive 1's
// we want to set the current one to -1 and start carrying
ret[i] = -1
carry = true
} else {
// this is a singleton, not consecutive 1's.
ret[i] = 1
if carry {
if curIsOne {
// This bit is 1, so we continue to carry and
// don't need to do anything
} else {
// We've hit a 0 after some number of 1s.
if nextIsOne {
// We start carrying again since we're starting
// a new sequence of 1s.
retNeg[i+1] += 1 << j
} else {
// We stop carrying since 1s have stopped.
carry = false
retPos[i+1] += 1 << j
}
}
} else if curIsOne {
if nextIsOne {
// if this is the start of at least 2 consecutive 1's
// we want to set the current one to -1 and start carrying
retNeg[i+1] += 1 << j
carry = true
} else {
// this is a singleton, not consecutive 1's.
retPos[i+1] += 1 << j
}
}
curByte >>= 1
}
}
if carry {
retPos[0] = 1
}

return ret
return retPos, retNeg
}

// ScalarMult returns k*(Bx, By) where k is a big endian integer.
Expand All @@ -750,14 +753,14 @@ func (curve *KoblitzCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big
// P1 below is P in the equation, P2 below is ϕ(P) in the equation
p1x, p1y := curve.bigAffineToField(Bx, By)
// For NAF, we need the negative point
p1yNeg := new(fieldVal).Set(p1y).Negate(1)
p1yNeg := new(fieldVal).NegateVal(p1y, 1)
p1z := new(fieldVal).SetInt(1)
// Note ϕ(x,y) = (βx,y), the Jacobian z coordinate is 1, so this math
// goes through.
p2x := new(fieldVal).Set(p1x).Mul(curve.beta)
p2x := new(fieldVal).Mul2(p1x, curve.beta)
p2y := new(fieldVal).Set(p1y)
// For NAF, we need the negative point
p2yNeg := new(fieldVal).Set(p2y).Negate(1)
p2yNeg := new(fieldVal).NegateVal(p2y, 1)
p2z := new(fieldVal).SetInt(1)

// If k1 or k2 are negative, we flip the positive/negative values
Expand All @@ -769,10 +772,12 @@ func (curve *KoblitzCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big
}

// NAF versions of k1 and k2 should have a lot more zeros
k1NAF := NAF(k1)
k2NAF := NAF(k2)
k1Len := len(k1NAF)
k2Len := len(k2NAF)
// the Pos version of the bytes contain the +1's and the Neg versions
// contain the -1's
k1PosNAF, k1NegNAF := NAF(k1)
k2PosNAF, k2NegNAF := NAF(k2)
k1Len := len(k1PosNAF)
k2Len := len(k2PosNAF)

m := k1Len
if m < k2Len {
Expand All @@ -784,32 +789,43 @@ func (curve *KoblitzCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big
// This should be faster overall since there will be a lot more instances
// of 0, hence reducing the number of Jacobian additions at the cost
// of 1 possible extra doubling.
var n1, n2 int
var k1BytePos, k1ByteNeg, k2BytePos, k2ByteNeg byte
for i := 0; i < m; i++ {
// Q = 2 * Q
curve.doubleJacobian(qx, qy, qz, qx, qy, qz)

// Since we're going left-to-right, we need to pad the front with 0's
if i < m-k1Len {
n1 = 0
k1BytePos = 0
k1ByteNeg = 0
} else {
n1 = k1NAF[i-m+k1Len]
}
if n1 == 1 {
curve.addJacobian(qx, qy, qz, p1x, p1y, p1z, qx, qy, qz)
} else if n1 == -1 {
curve.addJacobian(qx, qy, qz, p1x, p1yNeg, p1z, qx, qy, qz)
k1BytePos = k1PosNAF[i-(m-k1Len)]
k1ByteNeg = k1NegNAF[i-(m-k1Len)]
}
// Since we're going left-to-right, we need to pad the front with 0's
if i < m-k2Len {
n2 = 0
k2BytePos = 0
k2ByteNeg = 0
} else {
n2 = k2NAF[i-m+k2Len]
k2BytePos = k2PosNAF[i-(m-k2Len)]
k2ByteNeg = k2NegNAF[i-(m-k2Len)]
}
if n2 == 1 {
curve.addJacobian(qx, qy, qz, p2x, p2y, p2z, qx, qy, qz)
} else if n2 == -1 {
curve.addJacobian(qx, qy, qz, p2x, p2yNeg, p2z, qx, qy, qz)

for j := 7; j >= 0; j-- {
// Q = 2 * Q
curve.doubleJacobian(qx, qy, qz, qx, qy, qz)

if k1BytePos&0x80 == 0x80 {
curve.addJacobian(qx, qy, qz, p1x, p1y, p1z, qx, qy, qz)
} else if k1ByteNeg&0x80 == 0x80 {
curve.addJacobian(qx, qy, qz, p1x, p1yNeg, p1z, qx, qy, qz)
}

if k2BytePos&0x80 == 0x80 {
curve.addJacobian(qx, qy, qz, p2x, p2y, p2z, qx, qy, qz)
} else if k2ByteNeg&0x80 == 0x80 {
curve.addJacobian(qx, qy, qz, p2x, p2yNeg, p2z, qx, qy, qz)
}
k1BytePos <<= 1
k1ByteNeg <<= 1
k2BytePos <<= 1
k2ByteNeg <<= 1
}
}

Expand Down Expand Up @@ -875,6 +891,8 @@ func initS256() {
// Next 6 constants are from Hal Finney's bitcointalk.org post:
// https://bitcointalk.org/index.php?topic=3238.msg45565#msg45565
// May he rest in peace.
// These have been independently verified by Dave Collins using
// an ecc math script.
secp256k1.lambda, _ = new(big.Int).SetString("5363AD4CC05C30E0A5261C028812645A122E22EA20816678DF02967C1B23BD72", 16)
secp256k1.beta = new(fieldVal).SetHex("7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE")
secp256k1.a1, _ = new(big.Int).SetString("3086D221A7D46BCDE86C90E49284EB15", 16)
Expand Down
21 changes: 13 additions & 8 deletions btcec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,23 +667,28 @@ func TestNAF(t *testing.T) {
t.Fatalf("failed to read random data at %d", i)
break
}
naf := btcec.NAF(data)
nafPos, nafNeg := btcec.NAF(data)
want := new(big.Int).SetBytes(data)
got := big.NewInt(0)
// Check that the NAF representation comes up with the right number
for _, cur := range naf {
got.Mul(got, two)
if cur == 1 {
got.Add(got, one)
} else if cur == -1 {
got.Add(got, negOne)
for i := 0; i < len(nafPos); i++ {
bytePos := nafPos[i]
byteNeg := nafNeg[i]
for j := 7; j >= 0; j-- {
got.Mul(got, two)
if bytePos&0x80 == 0x80 {
got.Add(got, one)
} else if byteNeg&0x80 == 0x80 {
got.Add(got, negOne)
}
bytePos <<= 1
byteNeg <<= 1
}
}
if got.Cmp(want) != 0 {
t.Errorf("%d: Failed NAF got %X want %X", i, got, want)
}
}

}

func fromHex(s string) *big.Int {
Expand Down

0 comments on commit 1183b60

Please sign in to comment.