summaryrefslogtreecommitdiff
path: root/content/development/golang_spake2_groups.rst
diff options
context:
space:
mode:
authorVasudev Kamath <vasudev@copyninja.info>2018-09-02 17:14:33 +0530
committerVasudev Kamath <vasudev@copyninja.info>2018-09-02 17:14:33 +0530
commit55295cc860b2017cb79569a777beee3df536594b (patch)
tree1bc44bddc6197962b20e5c5174896fddb741fdb4 /content/development/golang_spake2_groups.rst
parent939257c9d68e669cc03737de7be55e2ef50b619e (diff)
New post on implementing groups in golang.
Diffstat (limited to 'content/development/golang_spake2_groups.rst')
-rw-r--r--content/development/golang_spake2_groups.rst482
1 files changed, 454 insertions, 28 deletions
diff --git a/content/development/golang_spake2_groups.rst b/content/development/golang_spake2_groups.rst
index b1a496a..c1e3258 100644
--- a/content/development/golang_spake2_groups.rst
+++ b/content/development/golang_spake2_groups.rst
@@ -1,11 +1,10 @@
SPAKE2 in Golang: Implementing Groups in Golang
###############################################
-:date: 2018-08-28 14:12 +0530
+:date: 2018-09-02 17:14 +0530
:slug: golang_spake2_5
:tags: go, golang, spake2, cryptography
:author: copyninja
-:status: draft
:summary: Fifth post in "SPAKE2 in Golang" series. This post is my
implementation notes for groups in Golang
@@ -20,15 +19,15 @@ In the Beginning ...
Like always I had too many question on how to do? what to do? etc. etc. Well
there is no definitive answer, you have to experiment to get the answer. So as a
first step I had to decide what I need. Looking at Python implementation of
-SPAKE2 I noticed some basic group operations common for all groups. Some of them
-are group operations listed below.
+SPAKE2 some basic group operations needed are as follows.
* Element Addition for the group
* Scalar Multiplication for the group
* Scalar Multiplication with Base point/Generator of group
-Along with this I noticed some other functions which are needed, these are not
-exactly group related operations, but are needed in SPAKE2 calculation.
+Along with this some other functions are needed, these are not exactly group
+related operations, but are needed in SPAKE2 calculation. We can call them
+helper operations and they are listed as follows.
* Convert password into Scalar of the group (Scalar is nothing but big integer
in the group)
@@ -36,21 +35,23 @@ exactly group related operations, but are needed in SPAKE2 calculation.
* Constants M, N and S.
* Converting group element to bytes and creating element from bytes.
* Element Size for the group.
+* Order of the subgroup
Now you might be wondering what `S` might be, as I only talked about `M` and `N`
-while explaining SPAKE2. Brian has created a special mode called **Symmetric
-Mode**. This is a special mode created by Brian Warner with help from other
+while explaining SPAKE2. This is for a special mode called **Symmetric
+Mode**. This special mode created by Brian Warner with help from other
cryptographic folks for **magic-wormhole** use case. This mode removes the
-side/identity from the SPAKE2 protocol above (A, B) and makes both side
-identical. This will reduce additional exchanges that had to be done to setup
-side/identities for both peers.
+side/identity from the SPAKE2 protocol as we saw in previous post (A, B) and
+makes both side identical. This will reduce additional exchanges that had to be
+done to setup side/identities for both peers.
Next question was whether SPAKE2 implementation is going to be Ed25519 group
specific or whether I plan to introduce other groups as well?. This was
important decision I had to make, since Python version also has some integer
-groups I decided to make groups configurable in Golang as well.
+groups I decided to make groups configurable in Golang as well with Ed25519 as
+default group.
-Defininig Group Interface
+Defining Group Interface
=========================
To make group as configurable in SPAKE2 package I had to define a generic type.
@@ -80,24 +81,26 @@ flexibility. As a first try I defined **Group** interface as follows
Remember this was not final version which I reached but initial version. I kept
this under `group.go` file. I also thought it would be better to keep group
-package outside SPAKE2. So I created SPAKE2 as
-`salsa.debian.org/vasudev/gospake2` and Ed25519 group operations under
-`salsa.debian.org/vasudev/ed25519group`.
+implementations outside SPAKE2. So I created SPAKE2 as
+`salsa.debian.org/vasudev/gospake2` Ed25519 group operations under
+`salsa.debian.org/vasudev/ed25519group` and integergroup operations under
+`salsa.debian.org/vasudev/integergroup`.
I hit my first road block with this representation. I created a type `Ed25519`
-in `ed25519group` package and implemented all above function but definitions
-were returning/accepting type `Ed25519` wherever `Group` was needed. My
-expectation was this should work because `Ed25519` confirms to `Group` interface
-but Go compiler thought otherwise!. Go compiler refused to agree `Ed25519` type
+in `ed25519group` package and implemented the `Group` interface above but
+instead of function accepting/returning `Group` I used `Ed25519`. This felt
+natural to me as `Ed25519` was confimring to `Group` interface but that Go
+compiler thought otherwise. Go compiler refused to agree `Ed25519` type
implemented `Group` interface and told the defintion of functions do not match.
-It said for ConstM, **expected return value of Group but found Ed25519**. I did
-not get why this does not work but from my understanding of compilers, I think
-when Go compiler scans ConstM line it still does not know that type `Ed25519`
-implements `Group` interface. I could have asked in forums but frankly I did not
-know how to phrase this question :). If you are a gopher and know the answer
-please do let me know :).
+For eg. it said for ConstM, **expected return value of Group but found
+Ed25519**. I did not yet figure out why this does not work, but as far as I can
+understand this happens because when Go compiler scans ConstM line it still does
+not know that type `Ed25519` implements `Group` interface. I could have asked in
+forums but frankly I did not know how to phrase this question :). If you are a
+gopher and know the answer please do let me know :).
-So once I hit the road block I modified definition to look like
+Without spending too much time to figure out why its not working, I changed the
+interface definition to look like below.
.. code:: go
@@ -119,4 +122,427 @@ So once I hit the road block I modified definition to look like
ElementSize() int
}
-So now compiler is happy because `interface{}` means any type.
+So now compiler is happy because `interface{}` means any type. Though I was not
+happy because I had to do lot of type assertions in the actual implementation of
+group.
+
+After I did first version of ed25519group and successfuly used it in gospake2
+0.1.0, I was feeling something was not correct and things needs to be improved.
+Then when I started to implement `integergroup` package things started becoming
+more clear to me. I finished writing `integergroup` with same interface
+definition as above.
+
+After both groups are implemented and integrated into gospake2, I started to
+look at python code moe carefully. A pattern started emerging in my mind. Python
+code was structured to differentiate Group and its elements, and this seemed
+natural separation. Once you separat Elements your interface definition will
+become more simpler.
+
+So after struggling a bit I wrote a new interface, now differentiating Elements
+and Group itself. The final code as of writing this post is below.
+
+.. code:: go
+
+ // Element represents the operation that needs to be satisfied by Group element.
+ type Element interface {
+ Add(other Element) Element
+ ScalarMult(s *big.Int) Element
+ Negate() Element
+ }
+
+ // Group defines methods that needs to be implemented by the number / elliptic
+ // curve group which is used to implement SPAKE2 algorithm
+ type Group interface {
+ // These functions are not really group operations but they are needed
+ // to get the required group Element's needed for calculation of SPAKE2
+ ConstM() Element
+ ConstN() Element
+ ConstS() Element
+
+ // This operation is needed to get a random integer in the group
+ RandomScalar() (*big.Int, error)
+
+ // This operation is for converting user password to a group element
+ PasswordToScalar(pw []byte) *big.Int
+
+ // These operations are group operations
+ BasePointMult(s *big.Int) Element
+ Add(a, b Element) Element
+ ScalarMult(a Element, s *big.Int) Element
+
+ ElementToBytes(e Element) []byte
+ ElementFromBytes([]byte) (Element, error)
+
+ // This operation should return size of the group
+ ElementSize() int
+
+ // This operation returns order of subgroup
+ Order() *big.Int
+ }
+
+
+Element interface requires implementer to implement `Add`, `ScalarMult` and
+`Negate` function. Group interface also has `Add` and `ScalarMult` operation but
+Group functions require you to pass Element as input and returns Element as
+output. Though it may be redundant it gives a natural organization to code.
+
+With new interface new group implementations don't have to do too much type
+assertions but there will still be some which can't be avoided (eg. Element to
+actual type).
+
+
+Packages, Subpackages and....
+=============================
+
+Well there is no such thing called subpackage in Go, this is one of the learning
+I had while writing *gospake2* and related *group* implementation. I first
+created the *Group* interface in file called `group.go` which was under
+`salsa.debian.org/vasudev/gospake2` package. So to refer Group interface I just
+need to import gospake2 and refer it as `gospake2.Group`. In the beginning this
+seemed correct approach as I did not directly refer the `Group` interface in the
+first versions of `ed25519group` and `integergroup`. (The version where I used
+`interface{}` extensively). But when I refactored to have 2 interfaces above I
+got cyclic dependency error. ed25519group and integergroup both referred
+gospake2.Group and gospake2 referred these groups.
+
+So to fix the error I moved the interface declaration from *group.go* to
+*groups* folder under gospake2 package, and made it package *groups*. Few
+points I learned while doing this is
+
+* Even if the package is inside your package you can't directly use it. i.e.
+ there is no such thing as subpackage. gospake2 had to refer groups with its
+ full namespace i.e. `salsa.debian.org/vasudev/gospake2/groups`
+* Folder structure inside package does not directly relate to each other, its
+ just placing them in meaningful path like `crypto/sha256` and `crypto/sha512`
+ they do not mean they are related its just that they fall under cryptography.
+* Standard library can refer to interface in parent package, for example
+ `crypto/ecdsa` can refer to `crypto.SignerOpts` interface which is defined in
+ crypto package just by importing "crypto" inside ecdsa package. This works
+ because *crypto* is package name in GOPATH, but there is nothing special here.
+ For us to refer something in so called parent package we need to use fullpath
+ for example `salsa.debian.org/vasudev/gospake2/groups` because that is how
+ user packages are namespaced under GOPATH.
+
+So finally I got a proper layout for `Group` and `Element` interfaces, its now
+available under `salsa.debian.org/vasudev/gospake2/groups` package. If you
+intend to provide a new group implementation for gospake2 you need to implement
+these interfaces in your package.
+
+Implementing Ed25519 Group
+==========================
+
+Implementing Ed25519 group was a bit of adventurous journey. First I searched
+for ready made implementation if any. Only thing I found was
+`golang.org/x/crypto/ed25519/internal/edwards25519` which is port of DJB's
+original C code to Go by Adam Langley. Problem was this package was
+internal to `ed25519` package and Go compiler would refuse to allow you import it
+outside the ed25519 package. So I decided to embed *edwards25519* as a internal
+package with in `gospake2`. This was prior to second version of `Group` interface
+design.
+
+Even with embedding I could not really use it properly. I could get the
+BasePointMult operation working but nothing else worked and naively I tried to
+use `ScMulAdd` for Scalar multiplication which was really a wrong thing to do.
+Later I understood that the module was specifically written for Ed25519
+signature scheme. Though it might still be possible to use it now that I've
+understood the basics of curve, I will definitely give it a second try at later
+point in time.
+
+After the failed attempt with edwards25519 Ramakrishnan suggested me to use big
+integer and implement those methods myself and finally that is what I did. I
+used `math/big` package to implement the operations required myself. So the
+experience of writing this module taught me a lot. Below are few of my
+learnings.
+
+Annoyance with big.Int
+----------------------
+
+While using big.Int I was annoyed by the specific syntax which invovled invoking
+the operation using a big.Int variable which will set the result to same
+variable and additionally return same value also. This design felt redundant to
+me and also I had to create so many intermediate variables to get operations
+like Add or Double implemented. Are you thinking why?. Then look at below
+formula for Add to add 2 points `P1 = (X1,Y1,T1,Z1)` and `P2 = (X2, Y2, T2. Z2)`
+
+.. code:: python
+
+ A = (Y1-X1)*(Y2-X2)
+ B = (Y1+X1)*(Y2+X2)
+ C = T1*k*T2
+ D = Z1*2*Z2
+ E = B-A
+ F = D-C
+ G = D+C
+ H = B+A
+ X3 = E*F
+ Y3 = G*H
+ T3 = E*H
+ Z3 = F*G
+
+With big.Int I had to create every intermediate variable and then calculate
+their result, for eg. (Y1-X1) and (Y2 - X2) and then finally calculate A. At
+this point I started liking C++ more as it will allow me to override + operator
+;-). But at later point I noticed some Go code where people dealt with the
+big.Int in following format
+
+.. code:: go
+
+ Y1MX1 := new(big.Int).Sub(Y1, X1)
+ Y2MX2 := new(big.Int).Sub(Y2, X2)
+ A := new(big.Int).Mul(Y1MX1, Y2MX2)
+
+It reduced intermediate variables to some extent, additionally it avoids
+declaring required variables first and then use it. But still its bit of
+annoyance :).
+
+Confusions with Pointers
+------------------------
+
+When using local variables of type big.Int and returning a pointer to
+it, I started to have a doubt that if what I'm doing is correct. Being from C
+background where you are not supposed to be returning pointer to a stack
+variable, Go's ability to return address of local variable confused me. But it
+looks like `Go compiler is smarter in this aspect
+<https://stackoverflow.com/questions/38234487/go-returning-a-pointer-on-stack#38234526>`_.
+Basically Go compiler does escape analysis to figure out if variable leaves
+after function and if so it moves it to garbage collected heap. So basically as
+Go programmer I need not bother on where my variables are allocated. Thats a
+relief :).
+
+
+Type Aliasing in Go
+-------------------
+
+Type aliasing in languages like Rust or C++ is a handy way to create alternate
+name for previously created type. This just creates a new name for existing type
+and you can still use the original types methods or variables. But this is not
+the same case in Go. Go `language spec
+<https://golang.org/ref/spec#Type_declarations>`_. clearly says that new type
+does not derive anything from originl type. But compiler allows you to cast
+to-and-fro from original to new type and vice versa.
+
+I was bit by this as I did not knew about it. I created a alias for
+ExtendedPoint type as Ed25519 to implement Group interface. But when I tried to
+access original functions from ExtendedPoint I noticed this behavior. So I had
+to write private conversion function just to cast types around.
+
+Implementing Scalar Multiplication and stack exhaustion
+-------------------------------------------------------
+
+Implementing scalar multiplication was one of the last adventure I tackled in
+the ed25519 group implementation. Scalar multiplication is multiplying a given
+elliptic curve point with a large integer (otherwise called as scalar) limited
+by subgroup order. Warner's python implementation was as follows
+
+.. code:: python
+
+ def scalarmult_element(pt, n): # extended->extended
+ # This form only works properly when given points that are a member of
+ # the main 1*L subgroup. It will give incorrect answers when called with
+ # the points of order 1/2/4/8, including point Zero. (it will also work
+ # properly when given points of order 2*L/4*L/8*L)
+ assert n >= 0
+ if n==0:
+ return xform_affine_to_extended((0,1))
+ _ = double_element(scalarmult_element(pt, n>>1))
+ return _add_elements_nonunfied(_, pt) if n&1 else _
+
+Though I don't exactly remember first version of my scalar multiplication
+function, it was mimicking the python code in Go with big.Int. The code worked
+well with small integers but when I gave big numbers generated using
+`RandomScalar` function of `Group` interface, code will panic as it will run out
+of stack.
+
+Above python code is slightly optimized version, so I looked at Haskell
+implementation of SPAKE2 which looked like below
+
+.. code:: haskell
+
+ -- | Scalar multiplication parametrised by addition.
+ scalarMultiplyExtendedPoint :: (ExtendedPoint a -> ExtendedPoint a -> ExtendedPoint a) -> Integer -> ExtendedPoint a -> ExtendedPoint a
+ scalarMultiplyExtendedPoint _ 0 _ = extendedZero
+ scalarMultiplyExtendedPoint add n x
+ | even n = doubleExtendedPoint (scalarMultiplyExtendedPoint add (n `div` 2) x)
+ | n == 1 = x
+ | n <= 0 = panic $ "Unexpected negative multiplier: " <> show n
+ | otherwise = add x (scalarMultiplyExtendedPoint add (n - 1) x)
+
+So algorithm is like this
+
+1. If scalar is 1 return same point
+2. If scalar is 0 return identity element for group
+3. If scalar is even then recursively call scalarmult by reducing the scalar to
+ half and double the result.
+4. Otherwise recursively scalarmultiply the point with scalar reduced by 1 and
+ add the result to the point.
+
+It might look slightly confusing explanation so I will just show the code below.
+
+.. code:: go
+
+ func (e *ExtendedPoint) ScalarMultSlow(s *big.Int) ExtendedPoint {
+ if s.Cmp(big.NewInt(0)) == 0 {
+ return Zero
+ }
+
+ if s.Cmp(big.NewInt(1)) == 0 {
+ return *e
+ }
+
+ var result ExtendedPoint
+ if IsEven(s) {
+ // If scalar is even we recursively call scalarmult with n/2 and
+ // then double the result.
+ result = e.ScalarMultSlow(new(big.Int).Rsh(s, 1))
+ result = result.Double()
+ } else {
+ // We decrement the scalar and recursively call scalarmult with
+ // it then we add the result with point
+ result = e.ScalarMultSlow(new(big.Int).Sub(s, big.NewInt(1)))
+ result = AddUnified(&result, e)
+ }
+
+ return result
+ }
+
+So instead of dividing by 2 I just right shift the scalar by 1 which is faster
+operation. (AddUnified is one of the algorithm for point addition which is more
+safer but slower alternative, hence the name ScalarMultSlow.)
+
+So this implementation works with every input, except the negative one for which
+I modified ScalarMult definition in Group level to reduce input scalar to
+subgroup order L. Otherwise Group function just calls function from Element.
+Code below.
+
+.. code:: go
+
+ // ScalarMult multiples given point with scalar and returns the result
+ func (e Ed25519) ScalarMult(a group.Element, s *big.Int) group.Element {
+ // First let's reduce s to curve order, this is important in case if we
+ // pass negated value
+ s.Mod(s, L)
+ if s.Cmp(big.NewInt(0)) == 0 {
+ return Zero
+ }
+
+ extendedPoint := a.(ExtendedPoint)
+ result := extendedPoint.ScalarMult(s)
+ return result
+ }
+
+Probably I shoud move reducing the scalar to subgroup order into scalarmult
+inside Element's implementation.
+
+Implementing Integer Group
+==========================
+
+Given the problems I faced and things I learnt, implementing Ed25519 group,
+implementing integer group was much straight forward. Only some design decisions
+had to be made.
+
+How to Represent Group and Elements
+-----------------------------------
+
+Unlike Ed25519 where group elements are basically points on the curve, element
+in multiplicative integer groups are basically integers. So how do I represent
+various integer groups?. Various integer groups are differentiated by bit length
+of elements in it, group and subgroup order. Looking at python code I created a
+structure called `GroupParameters` which will contain necessary information for
+a given group.
+
+.. code:: go
+
+ type GroupParameters struct {
+ p, q, g *big.Int
+ elementSizeBytes, elementSizeBits, scalarSize int
+ }
+
+I did not want to export these fields as they are not useful outside the
+package. Python code implemented 3 integer group of 1024,2048 and 3072 bit
+integers. All values for above variables were taken from `NIST document
+<http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf>`_.
+
+In first iteration I only had a struct called `IntegerGroup` which had
+parameters as member and implemented `Group` interface from gospake2. But when I
+refactored Group interface to have Element interface refactoring the code for
+integergroup became bit challenging. I introduced `IntegerElement` struct to
+hold actual integer value, but since all operations needed access to order of
+group I had to modify it to also contain parameters defined above. So final
+definition of `IntegerGroup` and `IntegerElement` is as follows
+
+.. code:: go
+
+ type IntegerGroup struct {
+ params *GroupParameters
+ }
+
+ type IntegerElement {
+ params *GroupParameters
+ e *big.Int
+ }
+
+Operations in `IntegerGroup` were simply calling functions from
+`IntegerElement`. So its really redundant but to make sure I can distinguish
+between both group and its element I had to use it in this form.
+
+Scalar Multiplication and Addition Operations
+---------------------------------------------
+
+Since the integer groups are really multiplicative group, addition operation is
+really a multiplication modulo p. Scalar multiplication is just exponentiation
+modulo p. Since these operations are readily available in `math/big` I did not
+had to do anything much for integer group. These operations in `IntegerElement`
+are defined as follows.
+
+.. code:: go
+
+ // Add is actually multiplication mod `p` where `p` is order of the
+ // multiplicative group
+ func (i IntegerElement) Add(other group.Element) group.Element {
+ a := i.e
+ b := other.(IntegerElement).e
+
+ if !i.params.equal(other.(IntegerElement).params) {
+ panic("You can't add elements of 2 different groups")
+ }
+
+ result := new(big.Int).Mul(a, b)
+
+ return group.Element(IntegerElement{params: i.params, e: result.Mod(result, i.params.p)})
+ }
+
+ // ScalarMult for multiplicative group is g^s mod p where `g` is group generator
+ // and p is order of the group
+ func (i IntegerElement) ScalarMult(s *big.Int) group.Element {
+ reducedS := new(big.Int).Mod(s, i.params.q)
+ return group.Element(IntegerElement{params: i.params, e: new(big.Int).Exp(i.e, reducedS, i.params.p)})
+ }
+
+Conclussion
+===========
+
+Well its been already a pretty long post, so without extending it more I would
+like to say that I had lot of learning experience in writing the Go code to
+implement these integer and ed25519 group. Main learnings were
+
+1. There is no definite answer for any questions, may it be how to write a
+ library or if I structured my library correctly. Of course there will be some
+ best practice available but you have to start at some point and then improve
+ it in iteration.
+2. Go provides great tooling especially linters and formatters which makes you
+ write a clean code. And also document all your exported functions as you
+ write (else you will keep seeing warnings in your editor which is annoying).
+3. Use your library yourself and you will see how you can improve it. If you are
+ feeling uncomfortable with your own written API then that means others will
+ too :).
+4. Every language is designed for a specific purpose, if I'm feeling discomfort
+ using some features of the language (I had problems with verbose error
+ handling) then probably I'm less experienced with language and should see how
+ others handle such things. There are many good projects which you can refer
+ to and lern from.
+
+In the next post which should be last in series I will write about design
+decisions I made writing gospake2 package. Code for both ed25519 and integer
+groups are now merged into gospake2 as that is the right place for them. You can
+find the code for them in my `gospake2 repo
+<https://salsa.debian.org/vasudev/gospake2>`_.