signing-key.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * Add details about signing here.
  3. *
  4. * @_subsection: api/crypto:Signing [about-signing]
  5. */
  6. import { secp256k1 } from "@noble/curves/secp256k1";
  7. import {
  8. concat, dataLength, getBytes, getBytesCopy, hexlify, toBeHex,
  9. assertArgument
  10. } from "../utils/index.js";
  11. import { Signature } from "./signature.js";
  12. import type { BytesLike } from "../utils/index.js";
  13. import type { SignatureLike } from "./index.js";
  14. /**
  15. * A **SigningKey** provides high-level access to the elliptic curve
  16. * cryptography (ECC) operations and key management.
  17. */
  18. export class SigningKey {
  19. #privateKey: string;
  20. /**
  21. * Creates a new **SigningKey** for %%privateKey%%.
  22. */
  23. constructor(privateKey: BytesLike) {
  24. assertArgument(dataLength(privateKey) === 32, "invalid private key", "privateKey", "[REDACTED]");
  25. this.#privateKey = hexlify(privateKey);
  26. }
  27. /**
  28. * The private key.
  29. */
  30. get privateKey(): string { return this.#privateKey; }
  31. /**
  32. * The uncompressed public key.
  33. *
  34. * This will always begin with the prefix ``0x04`` and be 132
  35. * characters long (the ``0x`` prefix and 130 hexadecimal nibbles).
  36. */
  37. get publicKey(): string { return SigningKey.computePublicKey(this.#privateKey); }
  38. /**
  39. * The compressed public key.
  40. *
  41. * This will always begin with either the prefix ``0x02`` or ``0x03``
  42. * and be 68 characters long (the ``0x`` prefix and 33 hexadecimal
  43. * nibbles)
  44. */
  45. get compressedPublicKey(): string { return SigningKey.computePublicKey(this.#privateKey, true); }
  46. /**
  47. * Return the signature of the signed %%digest%%.
  48. */
  49. sign(digest: BytesLike): Signature {
  50. assertArgument(dataLength(digest) === 32, "invalid digest length", "digest", digest);
  51. const sig = secp256k1.sign(getBytesCopy(digest), getBytesCopy(this.#privateKey), {
  52. lowS: true
  53. });
  54. return Signature.from({
  55. r: toBeHex(sig.r, 32),
  56. s: toBeHex(sig.s, 32),
  57. v: (sig.recovery ? 0x1c: 0x1b)
  58. });
  59. }
  60. /**
  61. * Returns the [[link-wiki-ecdh]] shared secret between this
  62. * private key and the %%other%% key.
  63. *
  64. * The %%other%% key may be any type of key, a raw public key,
  65. * a compressed/uncompressed pubic key or aprivate key.
  66. *
  67. * Best practice is usually to use a cryptographic hash on the
  68. * returned value before using it as a symetric secret.
  69. *
  70. * @example:
  71. * sign1 = new SigningKey(id("some-secret-1"))
  72. * sign2 = new SigningKey(id("some-secret-2"))
  73. *
  74. * // Notice that privA.computeSharedSecret(pubB)...
  75. * sign1.computeSharedSecret(sign2.publicKey)
  76. * //_result:
  77. *
  78. * // ...is equal to privB.computeSharedSecret(pubA).
  79. * sign2.computeSharedSecret(sign1.publicKey)
  80. * //_result:
  81. */
  82. computeSharedSecret(other: BytesLike): string {
  83. const pubKey = SigningKey.computePublicKey(other);
  84. return hexlify(secp256k1.getSharedSecret(getBytesCopy(this.#privateKey), getBytes(pubKey), false));
  85. }
  86. /**
  87. * Compute the public key for %%key%%, optionally %%compressed%%.
  88. *
  89. * The %%key%% may be any type of key, a raw public key, a
  90. * compressed/uncompressed public key or private key.
  91. *
  92. * @example:
  93. * sign = new SigningKey(id("some-secret"));
  94. *
  95. * // Compute the uncompressed public key for a private key
  96. * SigningKey.computePublicKey(sign.privateKey)
  97. * //_result:
  98. *
  99. * // Compute the compressed public key for a private key
  100. * SigningKey.computePublicKey(sign.privateKey, true)
  101. * //_result:
  102. *
  103. * // Compute the uncompressed public key
  104. * SigningKey.computePublicKey(sign.publicKey, false);
  105. * //_result:
  106. *
  107. * // Compute the Compressed a public key
  108. * SigningKey.computePublicKey(sign.publicKey, true);
  109. * //_result:
  110. */
  111. static computePublicKey(key: BytesLike, compressed?: boolean): string {
  112. let bytes = getBytes(key, "key");
  113. // private key
  114. if (bytes.length === 32) {
  115. const pubKey = secp256k1.getPublicKey(bytes, !!compressed);
  116. return hexlify(pubKey);
  117. }
  118. // raw public key; use uncompressed key with 0x04 prefix
  119. if (bytes.length === 64) {
  120. const pub = new Uint8Array(65);
  121. pub[0] = 0x04;
  122. pub.set(bytes, 1);
  123. bytes = pub;
  124. }
  125. const point = secp256k1.ProjectivePoint.fromHex(bytes);
  126. return hexlify(point.toRawBytes(compressed));
  127. }
  128. /**
  129. * Returns the public key for the private key which produced the
  130. * %%signature%% for the given %%digest%%.
  131. *
  132. * @example:
  133. * key = new SigningKey(id("some-secret"))
  134. * digest = id("hello world")
  135. * sig = key.sign(digest)
  136. *
  137. * // Notice the signer public key...
  138. * key.publicKey
  139. * //_result:
  140. *
  141. * // ...is equal to the recovered public key
  142. * SigningKey.recoverPublicKey(digest, sig)
  143. * //_result:
  144. *
  145. */
  146. static recoverPublicKey(digest: BytesLike, signature: SignatureLike): string {
  147. assertArgument(dataLength(digest) === 32, "invalid digest length", "digest", digest);
  148. const sig = Signature.from(signature);
  149. let secpSig = secp256k1.Signature.fromCompact(getBytesCopy(concat([ sig.r, sig.s ])));
  150. secpSig = secpSig.addRecoveryBit(sig.yParity);
  151. const pubKey = secpSig.recoverPublicKey(getBytesCopy(digest));
  152. assertArgument(pubKey != null, "invalid signautre for digest", "signature", signature);
  153. return "0x" + pubKey.toHex(false);
  154. }
  155. /**
  156. * Returns the point resulting from adding the ellipic curve points
  157. * %%p0%% and %%p1%%.
  158. *
  159. * This is not a common function most developers should require, but
  160. * can be useful for certain privacy-specific techniques.
  161. *
  162. * For example, it is used by [[HDNodeWallet]] to compute child
  163. * addresses from parent public keys and chain codes.
  164. */
  165. static addPoints(p0: BytesLike, p1: BytesLike, compressed?: boolean): string {
  166. const pub0 = secp256k1.ProjectivePoint.fromHex(SigningKey.computePublicKey(p0).substring(2));
  167. const pub1 = secp256k1.ProjectivePoint.fromHex(SigningKey.computePublicKey(p1).substring(2));
  168. return "0x" + pub0.add(pub1).toHex(!!compressed)
  169. }
  170. }