EcdsaSecp256k1RecoveryMethod2020.js

  1. const base64url = require("base64url");
  2. const ES256KR = require("./ES256K-R");
  3. class EcdsaSecp256k1RecoveryMethod2020 {
  4. /**
  5. * @param {KeyPairOptions} options - The options to use.
  6. * @param {string} options.id - The key ID.
  7. * @param {string} options.controller - The key controller.
  8. * @param {string} options.publicKeyJwk - The JWK encoded Public Key.
  9. * @param {string} options.privateKeyJwk - The JWK Private Key.
  10. * @param {string} options.publicKeyHex - The hex encoded compressed Public Key.
  11. * @param {string} options.privateKeyHex - The hex encoded compressed Private Key.
  12. * @param {string} options.ethereumAddress - The checksum encoded Ethereum Address.
  13. */
  14. constructor(options = {}) {
  15. Object.keys(options).forEach((k) => {
  16. this[k] = options[k];
  17. });
  18. if (options.type !== "EcdsaSecp256k1RecoveryMethod2020") {
  19. throw new Error(
  20. "EcdsaSecp256k1RecoveryMethod2020 is only supported type."
  21. );
  22. }
  23. }
  24. /**
  25. * Returns the public key.
  26. *
  27. * @returns {string | object} The public key.
  28. */
  29. get publicKey() {
  30. if (this.publicKeyJwk) {
  31. return this.publicKeyJwk;
  32. }
  33. if (this.publicKeyHex) {
  34. return this.publicKeyHex;
  35. }
  36. }
  37. /**
  38. * Returns a private key.
  39. *
  40. * @returns {string | object} The private key.
  41. */
  42. get privateKey() {
  43. if (this.privateKeyJwk) {
  44. return this.privateKeyJwk;
  45. }
  46. if (this.privateKeyHex) {
  47. return this.privateKeyHex;
  48. }
  49. }
  50. /**
  51. * Returns a signer object for use with jsonld-signatures.
  52. *
  53. * @returns {{sign: Function}} A signer for the json-ld block.
  54. */
  55. signer() {
  56. return joseSignerFactory(this);
  57. }
  58. /**
  59. * Returns a verifier object for use with jsonld-signatures.
  60. *
  61. * @returns {{verify: Function}} Used to verify jsonld-signatures.
  62. */
  63. verifier() {
  64. return joseVerifierFactory(this);
  65. }
  66. /**
  67. * Adds a public key base to a public key node.
  68. *
  69. * @param {Object} publicKeyNode - The public key node in a jsonld-signature.
  70. * @param {string} publicKeyNode.publicKeyJwk - JWK Public Key for
  71. * jsonld-signatures.
  72. * @param {string} publicKeyNode.publicKeyHex - Hex Public Key for
  73. * jsonld-signatures.
  74. * @param {string} publicKeyNode.ethereumAddress - ethereumAddress for
  75. * jsonld-signatures.
  76. *
  77. * @returns {Object} A PublicKeyNode in a block.
  78. */
  79. addEncodedPublicKey(publicKeyNode) {
  80. if (this.publicKeyJwk) {
  81. publicKeyNode.publicKeyJwk = this.publicKeyJwk;
  82. }
  83. if (this.publicKeyHex) {
  84. publicKeyNode.publicKeyHex = this.publicKeyHex;
  85. }
  86. if (this.ethereumAddress) {
  87. publicKeyNode.ethereumAddress = this.ethereumAddress;
  88. }
  89. return publicKeyNode;
  90. }
  91. static async from(options) {
  92. return new EcdsaSecp256k1RecoveryMethod2020(options);
  93. }
  94. /**
  95. * Contains the public key for the KeyPair
  96. * and other information that json-ld Signatures can use to form a proof.
  97. * @param {Object} [options={}] - Needs either a controller or owner.
  98. * @param {string} [options.controller=this.controller] - DID of the
  99. * person/entity controlling this key pair.
  100. *
  101. * @returns {Object} A public node with
  102. * information used in verification methods by signatures.
  103. */
  104. publicNode({ controller = this.controller } = {}) {
  105. const publicNode = {
  106. id: this.id,
  107. type: this.type,
  108. };
  109. if (controller) {
  110. publicNode.controller = controller;
  111. }
  112. this.addEncodedPublicKey(publicNode); // Subclass-specific
  113. return publicNode;
  114. }
  115. }
  116. /**
  117. * @ignore
  118. * Returns an object with an async sign function.
  119. * The sign function is bound to the KeyPair
  120. * and then returned by the KeyPair's signer method.
  121. * @param {EcdsaSecp256k1RecoveryMethod2020} key - An EcdsaSecp256k1RecoveryMethod2020.
  122. *
  123. * @returns {{sign: Function}} An object with an async function sign
  124. * using the private key passed in.
  125. */
  126. function joseSignerFactory(vm) {
  127. if (!vm.privateKeyJwk && !vm.privateKeyHex) {
  128. return {
  129. async sign() {
  130. throw new Error("No private vm to sign with.");
  131. },
  132. };
  133. }
  134. return {
  135. async sign({ data }) {
  136. const header = {
  137. alg: "ES256K-R",
  138. b64: false,
  139. crit: ["b64"],
  140. };
  141. toBeSigned = Buffer.from(data.buffer, data.byteOffset, data.length);
  142. return ES256KR.signDetached(toBeSigned, vm, header);
  143. },
  144. };
  145. }
  146. /**
  147. * @ignore
  148. * Returns an object with an async verify function.
  149. * The verify function is bound to the KeyPair
  150. * and then returned by the KeyPair's verifier method.
  151. * @param {EcdsaSecp256k1RecoveryMethod2020} key - An EcdsaSecp256k1RecoveryMethod2020.
  152. *
  153. * @returns {{verify: Function}} An async verifier specific
  154. * to the key passed in.
  155. */
  156. joseVerifierFactory = (vm) => {
  157. if (!vm.publicKeyJwk && !vm.publicKeyHex && !vm.ethereumAddress) {
  158. return {
  159. async sign() {
  160. throw new Error("No vm to verify with.");
  161. },
  162. };
  163. }
  164. return {
  165. async verify({ data, signature }) {
  166. const alg = "ES256K-R";
  167. const type = "EcdsaSecp256k1RecoveryMethod2020";
  168. const [encodedHeader, encodedSignature] = signature.split("..");
  169. let header;
  170. try {
  171. header = JSON.parse(base64url.decode(encodedHeader));
  172. } catch (e) {
  173. throw new Error("Could not parse JWS header; " + e);
  174. }
  175. if (!(header && typeof header === "object")) {
  176. throw new Error("Invalid JWS header.");
  177. }
  178. if (header.alg !== alg) {
  179. throw new Error(
  180. `Invalid JWS header, expected ${header.alg} === ${alg}.`
  181. );
  182. }
  183. // confirm header matches all expectations
  184. if (
  185. !(
  186. header.alg === alg &&
  187. header.b64 === false &&
  188. Array.isArray(header.crit) &&
  189. header.crit.length === 1 &&
  190. header.crit[0] === "b64"
  191. ) &&
  192. Object.keys(header).length === 3
  193. ) {
  194. throw new Error(
  195. `Invalid JWS header parameters ${JSON.stringify(header)} for ${type}.`
  196. );
  197. }
  198. let verified = false;
  199. const payload = Buffer.from(data.buffer, data.byteOffset, data.length);
  200. try {
  201. verified = ES256KR.verifyDetached(signature, payload, vm);
  202. } catch (e) {
  203. console.error("An error occurred when verifying signature: ", e);
  204. }
  205. return verified;
  206. },
  207. };
  208. };
  209. module.exports = EcdsaSecp256k1RecoveryMethod2020;