Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

417 linhas
16 KiB

  1. package cln16sidh
  2. // A point on the projective line P^1(F_{p^2}).
  3. //
  4. // XXX understand and explain what's going on with this as a moduli space
  5. type ProjectiveCurveParameters struct {
  6. A ExtensionFieldElement
  7. C ExtensionFieldElement
  8. }
  9. type CachedCurveParameters struct {
  10. Aplus2C ExtensionFieldElement
  11. C4 ExtensionFieldElement
  12. }
  13. // = 256
  14. var const256 = ExtensionFieldElement{
  15. a: fp751Element{0x249ad67, 0x0, 0x0, 0x0, 0x0, 0x730000000000000, 0x738154969973da8b, 0x856657c146718c7f, 0x461860e4e363a697, 0xf9fd6510bba838cd, 0x4e1a3c3f06993c0c, 0x55abef5b75c7},
  16. b: fp751Element{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
  17. }
  18. func (curveParams *ProjectiveCurveParameters) jInvariant() *ExtensionFieldElement {
  19. var v0, v1, v2, v3 ExtensionFieldElement
  20. A := &curveParams.A
  21. C := &curveParams.C
  22. v0.Square(C) // C^2
  23. v1.Square(A) // A^2
  24. v2.Add(&v0, &v0) // 2C^2
  25. v3.Add(&v2, &v0) // 3C^2
  26. v2.Add(&v2, &v2) // 4C^2
  27. v2.Sub(&v1, &v2) // A^2 - 4C^2
  28. v1.Sub(&v1, &v3) // A^2 - 3C^2
  29. v3.Square(&v1) // (A^2 - 3C^2)^2
  30. v3.Mul(&v3, &v1) // (A^2 - 3C^2)^3
  31. v0.Square(&v0) // C^4
  32. v3.Mul(&v3, &const256) // 256(A^2 - 3C^2)^3
  33. v2.Mul(&v2, &v0) // C^4(A^2 - 4C^2)
  34. v2.Inv(&v2) // 1/C^4(A^2 - 4C^2)
  35. v0.Mul(&v3, &v2) // 256(A^2 - 3C^2)^3 / C^4(A^2 - 4C^2)
  36. return &v0
  37. }
  38. // Compute cached parameters A + 2C, 4C.
  39. func (curve *ProjectiveCurveParameters) cachedParams() CachedCurveParameters {
  40. var cached CachedCurveParameters
  41. cached.Aplus2C.Add(&curve.C, &curve.C) // = 2*C
  42. cached.C4.Add(&cached.Aplus2C, &cached.Aplus2C) // = 4*C
  43. cached.Aplus2C.Add(&cached.Aplus2C, &curve.A) // = 2*C + A
  44. return cached
  45. }
  46. // A point on the projective line P^1(F_{p^2}).
  47. //
  48. // This represents a point on the (Kummer line) of a Montgomery curve. The
  49. // curve is specified by a ProjectiveCurveParameters struct.
  50. type ProjectivePoint struct {
  51. x ExtensionFieldElement // this is actually X, but can't be named that
  52. z ExtensionFieldElement // this is actually Z, but can't be named that
  53. }
  54. func (point *ProjectivePoint) toAffine() *ExtensionFieldElement {
  55. affine_x := new(ExtensionFieldElement)
  56. affine_x.Inv(&point.z).Mul(affine_x, &point.x)
  57. return affine_x
  58. }
  59. func (lhs *ProjectivePoint) VartimeEq(rhs *ProjectivePoint) bool {
  60. var t0, t1 ExtensionFieldElement
  61. t0.Mul(&lhs.x, &rhs.z)
  62. t1.Mul(&lhs.z, &rhs.x)
  63. return t0.VartimeEq(&t1)
  64. }
  65. func ProjectivePointConditionalSwap(xP, xQ *ProjectivePoint, choice uint8) {
  66. ExtensionFieldElementConditionalSwap(&xP.x, &xQ.x, choice)
  67. ExtensionFieldElementConditionalSwap(&xP.z, &xQ.z, choice)
  68. }
  69. // Given xP = x(P), xQ = x(Q), and xPmQ = x(P-Q), compute xR = x(P+Q).
  70. //
  71. // Returns xR to allow chaining. Safe to overlap xP, xQ, xR.
  72. func (xR *ProjectivePoint) Add(xP, xQ, xPmQ *ProjectivePoint) *ProjectivePoint {
  73. // Algorithm 1 of Costello-Smith.
  74. var v0, v1, v2, v3, v4 ExtensionFieldElement
  75. v0.Add(&xP.x, &xP.z) // X_P + Z_P
  76. v1.Sub(&xQ.x, &xQ.z).Mul(&v1, &v0) // (X_Q - Z_Q)(X_P + Z_P)
  77. v0.Sub(&xP.x, &xP.z) // X_P - Z_P
  78. v2.Add(&xQ.x, &xQ.z).Mul(&v2, &v0) // (X_Q + Z_Q)(X_P - Z_P)
  79. v3.Add(&v1, &v2).Square(&v3) // 4(X_Q X_P - Z_Q Z_P)^2
  80. v4.Sub(&v1, &v2).Square(&v4) // 4(X_Q Z_P - Z_Q X_P)^2
  81. v0.Mul(&xPmQ.z, &v3) // 4X_{P-Q}(X_Q X_P - Z_Q Z_P)^2
  82. xR.z.Mul(&xPmQ.x, &v4) // 4Z_{P-Q}(X_Q Z_P - Z_Q X_P)^2
  83. xR.x = v0
  84. return xR
  85. }
  86. // Given xP = x(P) and cached curve parameters Aplus2C = A + 2*C, C4 = 4*C, compute xQ = x([2]P).
  87. //
  88. // Returns xQ to allow chaining. Safe to overlap xP, xQ.
  89. func (xQ *ProjectivePoint) Double(xP *ProjectivePoint, curve *CachedCurveParameters) *ProjectivePoint {
  90. // Algorithm 2 of Costello-Smith, amended to work with projective curve coefficients.
  91. var v1, v2, v3, xz4 ExtensionFieldElement
  92. v1.Add(&xP.x, &xP.z).Square(&v1) // (X+Z)^2
  93. v2.Sub(&xP.x, &xP.z).Square(&v2) // (X-Z)^2
  94. xz4.Sub(&v1, &v2) // 4XZ = (X+Z)^2 - (X-Z)^2
  95. v2.Mul(&v2, &curve.C4) // 4C(X-Z)^2
  96. xQ.x.Mul(&v1, &v2) // 4C(X+Z)^2(X-Z)^2
  97. v3.Mul(&xz4, &curve.Aplus2C) // 4XZ(A + 2C)
  98. v3.Add(&v3, &v2) // 4XZ(A + 2C) + 4C(X-Z)^2
  99. xQ.z.Mul(&v3, &xz4) // (4XZ(A + 2C) + 4C(X-Z)^2)4XZ
  100. // Now (xQ.x : xQ.z)
  101. // = (4C(X+Z)^2(X-Z)^2 : (4XZ(A + 2C) + 4C(X-Z)^2)4XZ )
  102. // = ((X+Z)^2(X-Z)^2 : (4XZ((A + 2C)/4C) + (X-Z)^2)4XZ )
  103. // = ((X+Z)^2(X-Z)^2 : (4XZ((a + 2)/4) + (X-Z)^2)4XZ )
  104. return xQ
  105. }
  106. // Given the curve parameters, xP = x(P), and k >= 1, compute xQ = x([2^k]P).
  107. //
  108. // Returns xQ to allow chaining. Safe to overlap xP, xQ.
  109. func (xQ *ProjectivePoint) Pow2k(curve *ProjectiveCurveParameters, xP *ProjectivePoint, k uint32) *ProjectivePoint {
  110. if k == 0 {
  111. panic("Called Pow2k with k == 0")
  112. }
  113. cachedParams := curve.cachedParams()
  114. *xQ = *xP
  115. for i := uint32(0); i < k; i++ {
  116. xQ.Double(xQ, &cachedParams)
  117. }
  118. return xQ
  119. }
  120. // Given xP = x(P) and cached curve parameters Aplus2C = A + 2*C, C4 = 4*C, compute xQ = x([3]P).
  121. //
  122. // Returns xQ to allow chaining. Safe to overlap xP, xQ.
  123. func (xQ *ProjectivePoint) Triple(xP *ProjectivePoint, curve *CachedCurveParameters) *ProjectivePoint {
  124. // Uses the efficient Montgomery tripling formulas from Costello-Longa-Naehrig.
  125. var v0, v1, v2, v3, v4, v5 ExtensionFieldElement
  126. // Compute (X_2 : Z_2) = x([2]P)
  127. v2.Sub(&xP.x, &xP.z) // X - Z
  128. v3.Add(&xP.x, &xP.z) // X + Z
  129. v0.Square(&v2) // (X-Z)^2
  130. v1.Square(&v3) // (X+Z)^2
  131. v4.Mul(&v0, &curve.C4) // 4C(X-Z)^2
  132. v5.Mul(&v4, &v1) // 4C(X-Z)^2(X+Z)^2 = X_2
  133. v1.Sub(&v1, &v0) // (X+Z)^2 - (X-Z)^2 = 4XZ
  134. v0.Mul(&v1, &curve.Aplus2C) // 4XZ(A+2C)
  135. v4.Add(&v4, &v0).Mul(&v4, &v1) // (4C(X-Z)^2 + 4XZ(A+2C))4XZ = Z_2
  136. // Compute (X_3 : Z_3) = x(P + [2]P)
  137. v0.Add(&v5, &v4).Mul(&v0, &v2) // (X_2 + Z_2)(X-Z)
  138. v1.Sub(&v5, &v4).Mul(&v1, &v3) // (X_2 - Z_2)(X+Z)
  139. v4.Sub(&v0, &v1).Square(&v4) // 4(XZ_2 - ZX_2)^2
  140. v5.Add(&v0, &v1).Square(&v5) // 4(XX_2 - ZZ_2)^2
  141. v2.Mul(&xP.z, &v5) // 4Z(XX_2 - ZZ_2)^2
  142. xQ.z.Mul(&xP.x, &v4) // 4X(XZ_2 - ZX_2)^2
  143. xQ.x = v2
  144. return xQ
  145. }
  146. // Given the curve parameters, xP = x(P), and k >= 1, compute xQ = x([2^k]P).
  147. //
  148. // Returns xQ to allow chaining. Safe to overlap xP, xQ.
  149. func (xQ *ProjectivePoint) Pow3k(curve *ProjectiveCurveParameters, xP *ProjectivePoint, k uint32) *ProjectivePoint {
  150. if k == 0 {
  151. panic("Called Pow3k with k == 0")
  152. }
  153. cachedParams := curve.cachedParams()
  154. *xQ = *xP
  155. for i := uint32(0); i < k; i++ {
  156. xQ.Triple(xQ, &cachedParams)
  157. }
  158. return xQ
  159. }
  160. // Given x(P) and a scalar m in little-endian bytes, compute x([m]P) using the
  161. // Montgomery ladder. This is described in Algorithm 8 of Costello-Smith.
  162. //
  163. // This function's execution time is dependent only on the byte-length of the
  164. // input scalar. All scalars of the same input length execute in uniform time.
  165. // The scalar can be padded with zero bytes to ensure a uniform length.
  166. //
  167. // Safe to overlap the source with the destination.
  168. func (xQ *ProjectivePoint) ScalarMult(curve *ProjectiveCurveParameters, xP *ProjectivePoint, scalar []uint8) *ProjectivePoint {
  169. cachedParams := curve.cachedParams()
  170. var x0, x1, tmp ProjectivePoint
  171. x0.x.One()
  172. x0.z.Zero()
  173. x1 = *xP
  174. // Iterate over the bits of the scalar, top to bottom
  175. prevBit := uint8(0)
  176. for i := len(scalar) - 1; i >= 0; i-- {
  177. scalarByte := scalar[i]
  178. for j := 7; j >= 0; j-- {
  179. bit := (scalarByte >> uint(j)) & 0x1
  180. ProjectivePointConditionalSwap(&x0, &x1, (bit ^ prevBit))
  181. tmp.Double(&x0, &cachedParams)
  182. x1.Add(&x0, &x1, xP)
  183. x0 = tmp
  184. prevBit = bit
  185. }
  186. }
  187. // now prevBit is the lowest bit of the scalar
  188. ProjectivePointConditionalSwap(&x0, &x1, prevBit)
  189. *xQ = x0
  190. return xQ
  191. }
  192. // Given x(P), x(Q), x(P-Q), as well as a scalar m in little-endian bytes,
  193. // compute x(P + [m]Q) using the "three-point ladder" of de Feo, Jao, and Plut.
  194. //
  195. // Safe to overlap the source with the destination.
  196. //
  197. // This function's execution time is dependent only on the byte-length of the
  198. // input scalar. All scalars of the same input length execute in uniform time.
  199. // The scalar can be padded with zero bytes to ensure a uniform length.
  200. //
  201. // The algorithm, as described in de Feo-Jao-Plut, is as follows:
  202. //
  203. // (x0, x1, x2) <--- (x(O), x(Q), x(P))
  204. //
  205. // for i = |m| down to 0, indexing the bits of m:
  206. // Invariant: (x0, x1, x2) == (x( [t]Q ), x( [t+1]Q ), x( P + [t]Q ))
  207. // where t = m//2^i is the high bits of m, starting at i
  208. // if m_i == 0:
  209. // (x0, x1, x2) <--- (xDBL(x0), xADD(x1, x0, x(Q)), xADD(x2, x0, x(P)))
  210. // Invariant: (x0, x1, x2) == (x( [2t]Q ), x( [2t+1]Q ), x( P + [2t]Q ))
  211. // == (x( [t']Q ), x( [t'+1]Q ), x( P + [t']Q ))
  212. // where t' = m//2^{i-1} is the high bits of m, starting at i-1
  213. // if m_i == 1:
  214. // (x0, x1, x2) <--- (xADD(x1, x0, x(Q)), xDBL(x1), xADD(x2, x1, x(P-Q)))
  215. // Invariant: (x0, x1, x2) == (x( [2t+1]Q ), x( [2t+2]Q ), x( P + [2t+1]Q ))
  216. // == (x( [t']Q ), x( [t'+1]Q ), x( P + [t']Q ))
  217. // where t' = m//2^{i-1} is the high bits of m, starting at i-1
  218. // return x2
  219. //
  220. // Notice that the roles of (x0,x1) and (x(P), x(P-Q)) swap depending on the
  221. // current bit of the scalar. Instead of swapping which operations we do, we
  222. // can swap variable names, producing the following uniform algorithm:
  223. //
  224. // (x0, x1, x2) <--- (x(O), x(Q), x(P))
  225. // (y0, y1) <--- (x(P), x(P-Q))
  226. //
  227. // for i = |m| down to 0, indexing the bits of m:
  228. // (x0, x1) <--- SWAP( m_{i+1} xor m_i, (x0,x1) )
  229. // (y0, y1) <--- SWAP( m_{i+1} xor m_i, (y0,y1) )
  230. // (x0, x1, x2) <--- ( xDBL(x0), xADD(x1,x0,x(Q)), xADD(x2, x0, y0) )
  231. //
  232. // return x2
  233. //
  234. func (xR *ProjectivePoint) ThreePointLadder(curve *ProjectiveCurveParameters, xP, xQ, xPmQ *ProjectivePoint, scalar []uint8) *ProjectivePoint {
  235. cachedParams := curve.cachedParams()
  236. var x0, x1, x2, y0, y1, tmp ProjectivePoint
  237. // (x0, x1, x2) <--- (x(O), x(Q), x(P))
  238. x0.x.One()
  239. x0.z.Zero()
  240. x1 = *xQ
  241. x2 = *xP
  242. // (y0, y1) <--- (x(P), x(P-Q))
  243. y0 = *xP
  244. y1 = *xPmQ
  245. // Iterate over the bits of the scalar, top to bottom
  246. prevBit := uint8(0)
  247. for i := len(scalar) - 1; i >= 0; i-- {
  248. scalarByte := scalar[i]
  249. for j := 7; j >= 0; j-- {
  250. bit := (scalarByte >> uint(j)) & 0x1
  251. ProjectivePointConditionalSwap(&x0, &x1, (bit ^ prevBit))
  252. ProjectivePointConditionalSwap(&y0, &y1, (bit ^ prevBit))
  253. x2.Add(&x2, &x0, &y0) // = xADD(x2, x0, y0)
  254. tmp.Double(&x0, &cachedParams)
  255. x1.Add(&x1, &x0, xQ) // = xADD(x1, x0, x(Q))
  256. x0 = tmp // = xDBL(x0)
  257. prevBit = bit
  258. }
  259. }
  260. *xR = x2
  261. return xR
  262. }
  263. // Represents a 3-isogeny phi, holding the data necessary to evaluate phi.
  264. type ThreeIsogeny struct {
  265. x ExtensionFieldElement
  266. z ExtensionFieldElement
  267. }
  268. // Given a three-torsion point x3 = x(P_3) on the curve E_(A:C), construct the
  269. // three-isogeny phi : E_(A:C) -> E_(A:C)/<P_3> = E_(A':C').
  270. //
  271. // Returns a tuple (codomain, isogeny) = (E_(A':C'), phi).
  272. func ComputeThreeIsogeny(x3 *ProjectivePoint) (ProjectiveCurveParameters, ThreeIsogeny) {
  273. var isogeny ThreeIsogeny
  274. isogeny.x = x3.x
  275. isogeny.z = x3.z
  276. // We want to compute
  277. // (A':C') = (Z^4 + 18X^2Z^2 - 27X^4 : 4XZ^3)
  278. // To do this, use the identity 18X^2Z^2 - 27X^4 = 9X^2(2Z^2 - 3X^2)
  279. var codomain ProjectiveCurveParameters
  280. var v0, v1, v2, v3 ExtensionFieldElement
  281. v1.Square(&x3.x) // = X^2
  282. v0.Add(&v1, &v1).Add(&v1, &v0) // = 3X^2
  283. v1.Add(&v0, &v0).Add(&v1, &v0) // = 9X^2
  284. v2.Square(&x3.z) // = Z^2
  285. v3.Square(&v2) // = Z^4
  286. v2.Add(&v2, &v2) // = 2Z^2
  287. v0.Sub(&v2, &v0) // = 2Z^2 - 3X^2
  288. v1.Mul(&v1, &v0) // = 9X^2(2Z^2 - 3X^2)
  289. v0.Mul(&x3.x, &x3.z) // = XZ
  290. v0.Add(&v0, &v0) // = 2XZ
  291. codomain.A.Add(&v3, &v1) // = Z^4 + 9X^2(2Z^2 - 3X^2)
  292. codomain.C.Mul(&v0, &v2) // = 4XZ^3
  293. return codomain, isogeny
  294. }
  295. // Given a 3-isogeny phi and a point xP = x(P), compute x(Q), the x-coordinate
  296. // of the image Q = phi(P) of P under phi : E_(A:C) -> E_(A':C').
  297. //
  298. // The output xQ = x(Q) is then a point on the curve E_(A':C'); the curve
  299. // parameters are returned by the Compute3Isogeny function used to construct
  300. // phi.
  301. func (phi *ThreeIsogeny) Eval(xP *ProjectivePoint) ProjectivePoint {
  302. var xQ ProjectivePoint
  303. var t0, t1, t2 ExtensionFieldElement
  304. t0.Mul(&phi.x, &xP.x) // = X3*XP
  305. t1.Mul(&phi.z, &xP.z) // = Z3*XP
  306. t2.Sub(&t0, &t1) // = X3*XP - Z3*ZP
  307. t0.Mul(&phi.z, &xP.x) // = Z3*XP
  308. t1.Mul(&phi.x, &xP.z) // = X3*ZP
  309. t0.Sub(&t0, &t1) // = Z3*XP - X3*ZP
  310. t2.Square(&t2) // = (X3*XP - Z3*ZP)^2
  311. t0.Square(&t0) // = (Z3*XP - X3*ZP)^2
  312. xQ.x.Mul(&t2, &xP.x) // = XP*(X3*XP - Z3*ZP)^2
  313. xQ.z.Mul(&t0, &xP.z) // = ZP*(Z3*XP - X3*ZP)^2
  314. return xQ
  315. }
  316. // Represents a 4-isogeny phi, holding the data necessary to evaluate phi.
  317. type FourIsogeny struct {
  318. Xsq_plus_Zsq ExtensionFieldElement
  319. Xsq_minus_Zsq ExtensionFieldElement
  320. XZ2 ExtensionFieldElement
  321. Xpow4 ExtensionFieldElement
  322. Zpow4 ExtensionFieldElement
  323. }
  324. // Given a four-torsion point x4 = x(P_4) on the curve E_(A:C), compute the
  325. // coefficients of the codomain E_(A':C') of the four-isogeny phi : E_(A:C) ->
  326. // E_(A:C)/<P_4>.
  327. //
  328. // Returns a tuple (codomain, isogeny) = (E_(A':C') : phi).
  329. func ComputeFourIsogeny(x4 *ProjectivePoint) (ProjectiveCurveParameters, FourIsogeny) {
  330. var codomain ProjectiveCurveParameters
  331. var isogeny FourIsogeny
  332. var v0, v1 ExtensionFieldElement
  333. v0.Square(&x4.x) // = X4^2
  334. v1.Square(&x4.z) // = Z4^2
  335. isogeny.Xsq_plus_Zsq.Add(&v0, &v1) // = X4^2 + Z4^2
  336. isogeny.Xsq_minus_Zsq.Add(&v0, &v1) // = X4^2 - Z4^2
  337. isogeny.XZ2.Add(&x4.x, &x4.z) // = X4 + Z4
  338. isogeny.XZ2.Square(&isogeny.XZ2) // = X4^2 + Z4^2 + 2X4Z4
  339. isogeny.XZ2.Sub(&isogeny.XZ2, &isogeny.Xsq_plus_Zsq) // = 2X4Z4
  340. isogeny.Xpow4.Square(&v0) // = X4^4
  341. isogeny.Zpow4.Square(&v1) // = Z4^4
  342. v0.Add(&isogeny.Xpow4, &isogeny.Xpow4) // = 2X4^4
  343. v0.Sub(&v0, &isogeny.Zpow4) // = 2X4^4 - Z4^4
  344. codomain.A.Add(&v0, &v0) // = 2(2X4^4 - Z4^4)
  345. codomain.C = isogeny.Zpow4 // = Z4^4
  346. return codomain, isogeny
  347. }
  348. // Given a 4-isogeny phi and a point xP = x(P), compute x(Q), the x-coordinate
  349. // of the image Q = phi(P) of P under phi : E_(A:C) -> E_(A':C').
  350. //
  351. // The output xQ = x(Q) is then a point on the curve E_(A':C'); the curve
  352. // parameters are returned by the ComputeFourIsogeny function used to construct
  353. // phi.
  354. func (phi *FourIsogeny) Eval(xP *ProjectivePoint) ProjectivePoint {
  355. var xQ ProjectivePoint
  356. var t0, t1, t2 ExtensionFieldElement
  357. // We want to compute formula (7) of Costello-Longa-Naehrig, namely
  358. //
  359. // Xprime = (2*X_4*Z*Z_4 - (X_4^2 + Z_4^2)*X)*(X*X_4 - Z*Z_4)^2*X
  360. // Zprime = (2*X*X_4*Z_4 - (X_4^2 + Z_4^2)*Z)*(X_4*Z - X*Z_4)^2*Z
  361. //
  362. // To do this we adapt the method in the MSR implementation, which computes
  363. //
  364. // X_Q = Xprime*( 16*(X_4 + Z_4)*(X_4 - Z_4)*X_4^2*Z_4^4 )
  365. // Z_Q = Zprime*( 16*(X_4 + Z_4)*(X_4 - Z_4)*X_4^2*Z_4^4 )
  366. //
  367. t0.Mul(&xP.x, &phi.XZ2) // = 2*X*X_4*Z_4
  368. t1.Mul(&xP.z, &phi.Xsq_plus_Zsq) // = (X_4^2 + Z_4^2)*Z
  369. t0.Sub(&t0, &t1) // = -X_4^2*Z + 2*X*X_4*Z_4 - Z*Z_4^2
  370. t1.Mul(&xP.z, &phi.Xsq_minus_Zsq) // = (X_4^2 - Z_4^2)*Z
  371. t2.Sub(&t0, &t1).Square(&t2) // = 4*(X_4*Z - X*Z_4)^2*X_4^2
  372. t0.Mul(&t0, &t1).Add(&t0, &t0).Add(&t0, &t0) // = 4*(2*X*X_4*Z_4 - (X_4^2 + Z_4^2)*Z)*(X_4^2 - Z_4^2)*Z
  373. t1.Add(&t0, &t2) // = 4*(X*X_4 - Z*Z_4)^2*Z_4^2
  374. t0.Mul(&t0, &t2) // = Zprime * 16*(X_4 + Z_4)*(X_4 - Z_4)*X_4^2
  375. xQ.z.Mul(&t0, &phi.Zpow4) // = Zprime * 16*(X_4 + Z_4)*(X_4 - Z_4)*X_4^2*Z_4^4
  376. t2.Mul(&t2, &phi.Zpow4) // = 4*(X_4*Z - X*Z_4)^2*X_4^2*Z_4^4
  377. t0.Mul(&t1, &phi.Xpow4) // = 4*(X*X_4 - Z*Z_4)^2*X_4^4*Z_4^2
  378. t0.Sub(&t2, &t0) // = -4*(X*X_4^2 - 2*X_4*Z*Z_4 + X*Z_4^2)*X*(X_4^2 - Z_4^2)*X_4^2*Z_4^2
  379. xQ.x.Mul(&t1, &t0) // = Xprime * 16*(X_4 + Z_4)*(X_4 - Z_4)*X_4^2*Z_4^4
  380. return xQ
  381. }