montgomery.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
  2. import { mod, pow } from './modular.js';
  3. import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
  4. const _0n = BigInt(0);
  5. const _1n = BigInt(1);
  6. function validateOpts(curve) {
  7. validateObject(curve, {
  8. a: 'bigint',
  9. }, {
  10. montgomeryBits: 'isSafeInteger',
  11. nByteLength: 'isSafeInteger',
  12. adjustScalarBytes: 'function',
  13. domain: 'function',
  14. powPminus2: 'function',
  15. Gu: 'bigint',
  16. });
  17. // Set defaults
  18. return Object.freeze({ ...curve });
  19. }
  20. // NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
  21. // Uses only one coordinate instead of two
  22. export function montgomery(curveDef) {
  23. const CURVE = validateOpts(curveDef);
  24. const { P } = CURVE;
  25. const modP = (n) => mod(n, P);
  26. const montgomeryBits = CURVE.montgomeryBits;
  27. const montgomeryBytes = Math.ceil(montgomeryBits / 8);
  28. const fieldLen = CURVE.nByteLength;
  29. const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes) => bytes);
  30. const powPminus2 = CURVE.powPminus2 || ((x) => pow(x, P - BigInt(2), P));
  31. // cswap from RFC7748. But it is not from RFC7748!
  32. /*
  33. cswap(swap, x_2, x_3):
  34. dummy = mask(swap) AND (x_2 XOR x_3)
  35. x_2 = x_2 XOR dummy
  36. x_3 = x_3 XOR dummy
  37. Return (x_2, x_3)
  38. Where mask(swap) is the all-1 or all-0 word of the same length as x_2
  39. and x_3, computed, e.g., as mask(swap) = 0 - swap.
  40. */
  41. function cswap(swap, x_2, x_3) {
  42. const dummy = modP(swap * (x_2 - x_3));
  43. x_2 = modP(x_2 - dummy);
  44. x_3 = modP(x_3 + dummy);
  45. return [x_2, x_3];
  46. }
  47. // Accepts 0 as well
  48. function assertFieldElement(n) {
  49. if (typeof n === 'bigint' && _0n <= n && n < P)
  50. return n;
  51. throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
  52. }
  53. // x25519 from 4
  54. // The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
  55. const a24 = (CURVE.a - BigInt(2)) / BigInt(4);
  56. /**
  57. *
  58. * @param pointU u coordinate (x) on Montgomery Curve 25519
  59. * @param scalar by which the point would be multiplied
  60. * @returns new Point on Montgomery curve
  61. */
  62. function montgomeryLadder(pointU, scalar) {
  63. const u = assertFieldElement(pointU);
  64. // Section 5: Implementations MUST accept non-canonical values and process them as
  65. // if they had been reduced modulo the field prime.
  66. const k = assertFieldElement(scalar);
  67. const x_1 = u;
  68. let x_2 = _1n;
  69. let z_2 = _0n;
  70. let x_3 = u;
  71. let z_3 = _1n;
  72. let swap = _0n;
  73. let sw;
  74. for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
  75. const k_t = (k >> t) & _1n;
  76. swap ^= k_t;
  77. sw = cswap(swap, x_2, x_3);
  78. x_2 = sw[0];
  79. x_3 = sw[1];
  80. sw = cswap(swap, z_2, z_3);
  81. z_2 = sw[0];
  82. z_3 = sw[1];
  83. swap = k_t;
  84. const A = x_2 + z_2;
  85. const AA = modP(A * A);
  86. const B = x_2 - z_2;
  87. const BB = modP(B * B);
  88. const E = AA - BB;
  89. const C = x_3 + z_3;
  90. const D = x_3 - z_3;
  91. const DA = modP(D * A);
  92. const CB = modP(C * B);
  93. const dacb = DA + CB;
  94. const da_cb = DA - CB;
  95. x_3 = modP(dacb * dacb);
  96. z_3 = modP(x_1 * modP(da_cb * da_cb));
  97. x_2 = modP(AA * BB);
  98. z_2 = modP(E * (AA + modP(a24 * E)));
  99. }
  100. // (x_2, x_3) = cswap(swap, x_2, x_3)
  101. sw = cswap(swap, x_2, x_3);
  102. x_2 = sw[0];
  103. x_3 = sw[1];
  104. // (z_2, z_3) = cswap(swap, z_2, z_3)
  105. sw = cswap(swap, z_2, z_3);
  106. z_2 = sw[0];
  107. z_3 = sw[1];
  108. // z_2^(p - 2)
  109. const z2 = powPminus2(z_2);
  110. // Return x_2 * (z_2^(p - 2))
  111. return modP(x_2 * z2);
  112. }
  113. function encodeUCoordinate(u) {
  114. return numberToBytesLE(modP(u), montgomeryBytes);
  115. }
  116. function decodeUCoordinate(uEnc) {
  117. // Section 5: When receiving such an array, implementations of X25519
  118. // MUST mask the most significant bit in the final byte.
  119. const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
  120. if (fieldLen === 32)
  121. u[31] &= 127; // 0b0111_1111
  122. return bytesToNumberLE(u);
  123. }
  124. function decodeScalar(n) {
  125. const bytes = ensureBytes('scalar', n);
  126. const len = bytes.length;
  127. if (len !== montgomeryBytes && len !== fieldLen)
  128. throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
  129. return bytesToNumberLE(adjustScalarBytes(bytes));
  130. }
  131. function scalarMult(scalar, u) {
  132. const pointU = decodeUCoordinate(u);
  133. const _scalar = decodeScalar(scalar);
  134. const pu = montgomeryLadder(pointU, _scalar);
  135. // The result was not contributory
  136. // https://cr.yp.to/ecdh.html#validate
  137. if (pu === _0n)
  138. throw new Error('Invalid private or public key received');
  139. return encodeUCoordinate(pu);
  140. }
  141. // Computes public key from private. By doing scalar multiplication of base point.
  142. const GuBytes = encodeUCoordinate(CURVE.Gu);
  143. function scalarMultBase(scalar) {
  144. return scalarMult(scalar, GuBytes);
  145. }
  146. return {
  147. scalarMult,
  148. scalarMultBase,
  149. getSharedSecret: (privateKey, publicKey) => scalarMult(privateKey, publicKey),
  150. getPublicKey: (privateKey) => scalarMultBase(privateKey),
  151. utils: { randomPrivateKey: () => CURVE.randomBytes(CURVE.nByteLength) },
  152. GuBytes: GuBytes,
  153. };
  154. }
  155. //# sourceMappingURL=montgomery.js.map