secp256k1-compat.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { sha256 } from "@noble/hashes/sha256";
  2. import { mod } from "@noble/curves/abstract/modular";
  3. import { secp256k1 } from "./secp256k1.js";
  4. import { assertBool, assertBytes, hexToBytes, toHex } from "./utils.js";
  5. // Use `secp256k1` module directly.
  6. // This is a legacy compatibility layer for the npm package `secp256k1` via noble-secp256k1
  7. const Point = secp256k1.ProjectivePoint;
  8. function hexToNumber(hex) {
  9. if (typeof hex !== "string") {
  10. throw new TypeError("hexToNumber: expected string, got " + typeof hex);
  11. }
  12. return BigInt(`0x${hex}`);
  13. }
  14. // Copy-paste from secp256k1, maybe export it?
  15. const bytesToNumber = (bytes) => hexToNumber(toHex(bytes));
  16. const numberToHex = (num) => num.toString(16).padStart(64, "0");
  17. const numberToBytes = (num) => hexToBytes(numberToHex(num));
  18. const ORDER = secp256k1.CURVE.n;
  19. function output(out = (len) => new Uint8Array(len), length, value) {
  20. if (typeof out === "function") {
  21. out = out(length);
  22. }
  23. assertBytes(out, length);
  24. if (value) {
  25. out.set(value);
  26. }
  27. return out;
  28. }
  29. function getSignature(signature) {
  30. assertBytes(signature, 64);
  31. return secp256k1.Signature.fromCompact(signature);
  32. }
  33. export function createPrivateKeySync() {
  34. return secp256k1.utils.randomPrivateKey();
  35. }
  36. export async function createPrivateKey() {
  37. return createPrivateKeySync();
  38. }
  39. export function privateKeyVerify(privateKey) {
  40. assertBytes(privateKey, 32);
  41. return secp256k1.utils.isValidPrivateKey(privateKey);
  42. }
  43. export function publicKeyCreate(privateKey, compressed = true, out) {
  44. assertBytes(privateKey, 32);
  45. assertBool(compressed);
  46. const res = secp256k1.getPublicKey(privateKey, compressed);
  47. return output(out, compressed ? 33 : 65, res);
  48. }
  49. export function publicKeyVerify(publicKey) {
  50. assertBytes(publicKey, 33, 65);
  51. try {
  52. Point.fromHex(publicKey);
  53. return true;
  54. }
  55. catch (e) {
  56. return false;
  57. }
  58. }
  59. export function publicKeyConvert(publicKey, compressed = true, out) {
  60. assertBytes(publicKey, 33, 65);
  61. assertBool(compressed);
  62. const res = Point.fromHex(publicKey).toRawBytes(compressed);
  63. return output(out, compressed ? 33 : 65, res);
  64. }
  65. export function ecdsaSign(msgHash, privateKey, options = { noncefn: undefined, data: undefined }, out) {
  66. assertBytes(msgHash, 32);
  67. assertBytes(privateKey, 32);
  68. if (typeof options !== "object" || options === null) {
  69. throw new TypeError("secp256k1.ecdsaSign: options should be object");
  70. }
  71. // noble-secp256k1 uses hmac instead of hmac-drbg here
  72. if (options &&
  73. (options.noncefn !== undefined || options.data !== undefined)) {
  74. throw new Error("Secp256k1: noncefn && data is unsupported");
  75. }
  76. const sig = secp256k1.sign(msgHash, privateKey);
  77. const recid = sig.recovery;
  78. return { signature: output(out, 64, sig.toCompactRawBytes()), recid };
  79. }
  80. export function ecdsaRecover(signature, recid, msgHash, compressed = true, out) {
  81. assertBytes(msgHash, 32);
  82. assertBool(compressed);
  83. const sign = getSignature(signature);
  84. const point = sign.addRecoveryBit(recid).recoverPublicKey(msgHash);
  85. return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
  86. }
  87. export function ecdsaVerify(signature, msgHash, publicKey) {
  88. assertBytes(signature, 64);
  89. assertBytes(msgHash, 32);
  90. assertBytes(publicKey, 33, 65);
  91. assertBytes(signature, 64);
  92. const r = bytesToNumber(signature.slice(0, 32));
  93. const s = bytesToNumber(signature.slice(32, 64));
  94. if (r >= ORDER || s >= ORDER) {
  95. throw new Error("Cannot parse signature");
  96. }
  97. const pub = Point.fromHex(publicKey); // can throw error
  98. pub; // typescript
  99. let sig;
  100. try {
  101. sig = getSignature(signature);
  102. }
  103. catch (error) {
  104. return false;
  105. }
  106. return secp256k1.verify(sig, msgHash, publicKey);
  107. }
  108. export function privateKeyTweakAdd(privateKey, tweak) {
  109. assertBytes(privateKey, 32);
  110. assertBytes(tweak, 32);
  111. let t = bytesToNumber(tweak);
  112. if (t === 0n) {
  113. throw new Error("Tweak must not be zero");
  114. }
  115. if (t >= ORDER) {
  116. throw new Error("Tweak bigger than curve order");
  117. }
  118. t += bytesToNumber(privateKey);
  119. if (t >= ORDER) {
  120. t -= ORDER;
  121. }
  122. if (t === 0n) {
  123. throw new Error("The tweak was out of range or the resulted private key is invalid");
  124. }
  125. privateKey.set(hexToBytes(numberToHex(t)));
  126. return privateKey;
  127. }
  128. export function privateKeyNegate(privateKey) {
  129. assertBytes(privateKey, 32);
  130. const bn = mod(-bytesToNumber(privateKey), ORDER);
  131. privateKey.set(hexToBytes(numberToHex(bn)));
  132. return privateKey;
  133. }
  134. export function publicKeyNegate(publicKey, compressed = true, out) {
  135. assertBytes(publicKey, 33, 65);
  136. assertBool(compressed);
  137. const point = Point.fromHex(publicKey).negate();
  138. return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
  139. }
  140. export function publicKeyCombine(publicKeys, compressed = true, out) {
  141. if (!Array.isArray(publicKeys) || !publicKeys.length) {
  142. throw new TypeError(`Expected array with one or more items, not ${publicKeys}`);
  143. }
  144. for (const publicKey of publicKeys) {
  145. assertBytes(publicKey, 33, 65);
  146. }
  147. assertBool(compressed);
  148. const combined = publicKeys
  149. .map((pub) => Point.fromHex(pub))
  150. .reduce((res, curr) => res.add(curr), Point.ZERO);
  151. // Prohibit returning ZERO point
  152. if (combined.equals(Point.ZERO)) {
  153. throw new Error("Combined result must not be zero");
  154. }
  155. return output(out, compressed ? 33 : 65, combined.toRawBytes(compressed));
  156. }
  157. export function publicKeyTweakAdd(publicKey, tweak, compressed = true, out) {
  158. assertBytes(publicKey, 33, 65);
  159. assertBytes(tweak, 32);
  160. assertBool(compressed);
  161. const p1 = Point.fromHex(publicKey);
  162. const p2 = Point.fromPrivateKey(tweak);
  163. const point = p1.add(p2);
  164. if (p2.equals(Point.ZERO) || point.equals(Point.ZERO)) {
  165. throw new Error("Tweak must not be zero");
  166. }
  167. return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
  168. }
  169. export function publicKeyTweakMul(publicKey, tweak, compressed = true, out) {
  170. assertBytes(publicKey, 33, 65);
  171. assertBytes(tweak, 32);
  172. assertBool(compressed);
  173. const bn = bytesToNumber(tweak);
  174. if (bn === 0n) {
  175. throw new Error("Tweak must not be zero");
  176. }
  177. if (bn <= 1 || bn >= ORDER) {
  178. throw new Error("Tweak is zero or bigger than curve order");
  179. }
  180. const point = Point.fromHex(publicKey).multiply(bn);
  181. return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
  182. }
  183. export function privateKeyTweakMul(privateKey, tweak) {
  184. assertBytes(privateKey, 32);
  185. assertBytes(tweak, 32);
  186. const bn = bytesToNumber(tweak);
  187. if (bn <= 1 || bn >= ORDER) {
  188. throw new Error("Tweak is zero or bigger than curve order");
  189. }
  190. const res = mod(bn * bytesToNumber(privateKey), ORDER);
  191. if (res === 0n) {
  192. throw new Error("The tweak was out of range or the resulted private key is invalid");
  193. }
  194. privateKey.set(hexToBytes(numberToHex(res)));
  195. return privateKey;
  196. }
  197. // internal -> DER
  198. export function signatureExport(signature, out) {
  199. const res = getSignature(signature).toDERRawBytes();
  200. return output(out, 72, res.slice()).slice(0, res.length);
  201. }
  202. // DER -> internal
  203. export function signatureImport(signature, out) {
  204. assertBytes(signature);
  205. const sig = secp256k1.Signature.fromDER(signature);
  206. return output(out, 64, hexToBytes(sig.toCompactHex()));
  207. }
  208. export function signatureNormalize(signature) {
  209. const res = getSignature(signature);
  210. if (res.s > ORDER / 2n) {
  211. signature.set(numberToBytes(ORDER - res.s), 32);
  212. }
  213. return signature;
  214. }
  215. export function ecdh(publicKey, privateKey, options = {}, out) {
  216. assertBytes(publicKey, 33, 65);
  217. assertBytes(privateKey, 32);
  218. if (typeof options !== "object" || options === null) {
  219. throw new TypeError("secp256k1.ecdh: options should be object");
  220. }
  221. if (options.data !== undefined) {
  222. assertBytes(options.data);
  223. }
  224. const point = Point.fromHex(secp256k1.getSharedSecret(privateKey, publicKey));
  225. if (options.hashfn === undefined) {
  226. return output(out, 32, sha256(point.toRawBytes(true)));
  227. }
  228. if (typeof options.hashfn !== "function") {
  229. throw new TypeError("secp256k1.ecdh: options.hashfn should be function");
  230. }
  231. if (options.xbuf !== undefined) {
  232. assertBytes(options.xbuf, 32);
  233. }
  234. if (options.ybuf !== undefined) {
  235. assertBytes(options.ybuf, 32);
  236. }
  237. assertBytes(out, 32);
  238. const { x, y } = point.toAffine();
  239. const xbuf = options.xbuf || new Uint8Array(32);
  240. xbuf.set(numberToBytes(x));
  241. const ybuf = options.ybuf || new Uint8Array(32);
  242. ybuf.set(numberToBytes(y));
  243. const hash = options.hashfn(xbuf, ybuf, options.data);
  244. if (!(hash instanceof Uint8Array) || hash.length !== 32) {
  245. throw new Error("secp256k1.ecdh: invalid options.hashfn output");
  246. }
  247. return output(out, 32, hash);
  248. }
  249. export function contextRandomize(seed) {
  250. if (seed !== null) {
  251. assertBytes(seed, 32);
  252. }
  253. // There is no context to randomize
  254. }