diff --git a/src/libs/crypto/index.ts b/src/libs/crypto/index.ts index db16413..8d8df5a 100644 --- a/src/libs/crypto/index.ts +++ b/src/libs/crypto/index.ts @@ -5,8 +5,44 @@ import { entropyToMnemonic } from "ethereum-cryptography/bip39"; import { wordlist } from "ethereum-cryptography/bip39/wordlists/english"; import { HDNode } from "@ethersproject/hdnode"; import * as didJwt from "did-jwt"; +import { Signer } from "did-jwt"; import * as u8a from "uint8arrays"; +export function hexToBytes(s: string): Uint8Array { + const input = s.startsWith("0x") ? s.substring(2) : s; + return u8a.fromString(input.toLowerCase(), "base16"); +} + +export function fromJose(signature: string): { + r: string; + s: string; + recoveryParam?: number; +} { + const signatureBytes: Uint8Array = base64ToBytes(signature); + if (signatureBytes.length < 64 || signatureBytes.length > 65) { + throw new TypeError( + `Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}` + ); + } + const r = bytesToHex(signatureBytes.slice(0, 32)); + const s = bytesToHex(signatureBytes.slice(32, 64)); + const recoveryParam = + signatureBytes.length === 65 ? signatureBytes[64] : undefined; + return { r, s, recoveryParam }; +} + +export function bytesToHex(b: Uint8Array): string { + return u8a.toString(b, "base16"); +} + +export function base64ToBytes(s: string): Uint8Array { + const inputBase64Url = s + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); + return u8a.fromString(inputBase64Url, "base64url"); +} + /** * * @@ -111,3 +147,23 @@ export const sign = async (privateKeyHex: string) => { return signer; }; + +/** + * The SimpleSigner returns a configured function for signing data. + * + * @example + * const signer = SimpleSigner(process.env.PRIVATE_KEY) + * signer(data, (err, signature) => { + * ... + * }) + * + * @param {String} hexPrivateKey a hex encoded private key + * @return {Function} a configured signer function + */ +export const SimpleSigner = async (hexPrivateKey: string): Promise => { + const signer = didJwt.ES256KSigner(hexToBytes(hexPrivateKey), true); + return async (data) => { + const signature = (await signer(data)) as string; + return fromJose(signature); + }; +}; diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index 211ec0c..bff74d3 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -74,8 +74,9 @@ import { Options, Vue } from "vue-class-component"; import { AppString } from "@/constants/app"; import { db } from "../db"; -import { accessToken, sign } from "@/libs/crypto"; +import { accessToken, SimpleSigner } from "@/libs/crypto"; import * as didJwt from "did-jwt"; +import { IIdentifier } from "@veramo/core"; @Options({ components: {}, @@ -84,40 +85,39 @@ export default class NewEditProjectView extends Vue { projectName = ""; description = ""; - public async onSaveProjectClick() { - await db.open(); - const num_accounts = await db.accounts.count(); - if (num_accounts === 0) { - console.log("Problem! Should have a profile!"); - } else { - const accounts = await db.accounts.toArray(); - const identity = JSON.parse(accounts[0].identity); - const address = identity.did; - // Make a claim - const vcClaim = { - "@context": "https://schema.org", - "@type": "PlanAction", - identifier: address, - name: this.projectName, - description: this.description, - }; - // Make a payload for the claim - const vcPayload = { - sub: "PlanAction", - vc: { - "@context": ["https://www.w3.org/2018/credentials/v1"], - type: ["VerifiableCredential"], - credentialSubject: vcClaim, - }, - }; - // create a signature using private key of identity - const signer = await sign(identity.keys[0].privateKeyHex); + private async SaveProject(identity: IIdentifier) { + const address = identity.did; + // Make a claim + const vcClaim = { + "@context": "https://schema.org", + "@type": "PlanAction", + identifier: address, + name: this.projectName, + description: this.description, + }; + // Make a payload for the claim + const vcPayload = { + sub: "PlanAction", + vc: { + "@context": ["https://www.w3.org/2018/credentials/v1"], + type: ["VerifiableCredential"], + credentialSubject: vcClaim, + }, + }; + // create a signature using private key of identity + if ( + identity.keys[0].privateKeyHex !== "undefined" && + identity.keys[0].privateKeyHex !== null + ) { + // eslint-disable-next-line + const privateKeyHex: string = identity.keys[0].privateKeyHex!; + const signer = await SimpleSigner(privateKeyHex); const alg = undefined; // create a JWT for the request const vcJwt: string = await didJwt.createJWT(vcPayload, { - alg, - issuer: identity, - signer, + alg: alg, + issuer: identity.did, + signer: signer, }); // Make the xhr request payload @@ -140,6 +140,18 @@ export default class NewEditProjectView extends Vue { } } + public async onSaveProjectClick() { + await db.open(); + const num_accounts = await db.accounts.count(); + if (num_accounts === 0) { + console.log("Problem! Should have a profile!"); + } else { + const accounts = await db.accounts.toArray(); + const identity = JSON.parse(accounts[0].identity); + this.SaveProject(identity); + } + } + public onCancelClick() { this.$router.back(); }