diff --git a/package-lock.json b/package-lock.json index f0a4300..d64e5f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zkfold-smart-wallet-api", - "version": "1.8.2", + "version": "1.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zkfold-smart-wallet-api", - "version": "1.8.2", + "version": "1.9.0", "license": "BUSL-1.1", "dependencies": { "@emurgo/cardano-serialization-lib-browser": "^14.1.1", @@ -3842,16 +3842,17 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.1.tgz", - "integrity": "sha512-tWDwrWy37CDVGeaP8AIGZPFL2RoFtmd5Y+nTzLw5qroXNedT2S66EY2d+XzB1zxulCd6nfDXnAQu4auq90aj5Q==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.1", - "@smithy/types": "^4.7.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.1", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -4043,15 +4044,15 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.1.tgz", - "integrity": "sha512-Ap8Wd95HCrWRktMAZNc0AVzdPdUSPHsG59+DMe+4aH74FLDnVTo/7XDcRhSkSZCHeDjaDtzAh5OvnHOE0VHwUg==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.1", - "@smithy/shared-ini-file-loader": "^4.3.1", - "@smithy/types": "^4.7.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4076,13 +4077,13 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.1.tgz", - "integrity": "sha512-2zthf6j/u4XV3nRvulJgQsZdAs9xNf7dJPE5+Wvrx4yAsNrmtchadydASqRLXEw67ovl8c+HFa58QEXD/jUMSg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4146,13 +4147,13 @@ } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.1.tgz", - "integrity": "sha512-V4XVUUCsuVeSNkjeXLR4Y5doyNkTx29Cp8NfKoklgpSsWawyxmJbVvJ1kFHRulOmdBlLuHoqDrAirN8ZoduUCA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4199,9 +4200,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.7.0.tgz", - "integrity": "sha512-KM8Or+jCDCrUI3wYYhj7ehrC7aATB1NdJ1aFEE/YLKNLVH257k9RNeOqKdg0JOxjyEpVD7KKsmmob9mRy1Ho2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4330,14 +4331,14 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.1.tgz", - "integrity": "sha512-lJudabG/ll+BD22i8IgxZgxS+1hEdUfFqtC1tNubC9vlGwInUktcXodTe5CvM+xDiqGZfqYLY7mKFdabCIrkYw==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.1", - "@smithy/types": "^4.7.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4358,13 +4359,13 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.1.tgz", - "integrity": "sha512-4rf5Ma0e0uuKmtzMihsvs3jnb9iGMRDWrUe6mfdZBWm52PW1xVHdEeP4+swhheF+YAXhVH/O+taKJuqOrVsG3w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -6803,9 +6804,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index e84984e..e77d9cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zkfold-smart-wallet-api", - "version": "1.8.2", + "version": "1.9.0", "description": "Smart Wallet API - Browser and extension compatible", "main": "dist/smart-wallet-api.es.js", "module": "dist/smart-wallet-api.es.js", diff --git a/src/Service/Backend.ts b/src/Service/Backend.ts index 2987b2a..14b4ff3 100644 --- a/src/Service/Backend.ts +++ b/src/Service/Backend.ts @@ -1,7 +1,7 @@ import * as CSL from '@emurgo/cardano-serialization-lib-browser'; import axios from 'axios'; import { serialize } from '../JSON'; -import { BigIntWrap, ProofBytes, Output, Reference, UTxO, CreateWalletResponse, SendFundsResponse, PrepareTxParameters, PrepareTxResponse, SubmitTxResult, ClientCredentials, Settings, BalanceResponse, Transaction } from '../Types' +import { BigIntWrap, ProofBytes, SigmaProof, Output, Reference, UTxO, CreateWalletResponse, SendFundsResponse, PrepareTxParameters, PrepareTxResponse, SubmitTxResult, ClientCredentials, Settings, BalanceResponse, Transaction } from '../Types' /** * A wrapper for interaction with the backend. @@ -10,6 +10,7 @@ import { BigIntWrap, ProofBytes, Output, Reference, UTxO, CreateWalletResponse, export class Backend { private url: string private secret: string | null + private apiVersion: number /** * Creates a new Backend object. @@ -19,6 +20,11 @@ export class Backend { constructor(url: string, secret: string | null = null) { this.url = url this.secret = secret + this.apiVersion = 0 + } + + public setApiVersion(version: number) { + this.apiVersion = version } private headers(additional: Record = {}) { @@ -40,7 +46,7 @@ export class Backend { * @returns {Settings} */ public async settings(): Promise { - const { data } = await axios.get(`${this.url}/v0/settings`, this.headers()) + const { data } = await axios.get(`${this.url}/v${this.apiVersion}/settings`, this.headers()) return data } @@ -50,7 +56,7 @@ export class Backend { * @returns {ClientCredentials} */ public async credentials(): Promise { - const { data } = await axios.get(`${this.url}/v0/oauth/credentials`, this.headers()) + const { data } = await axios.get(`${this.url}/v${this.apiVersion}/oauth/credentials`, this.headers()) return data } @@ -61,7 +67,7 @@ export class Backend { * @returns {CSL.Address} */ public async walletMainAddress(email: string): Promise { - const { data } = await axios.post(`${this.url}/v0/wallet/address`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/address`, { 'email': email }, this.headers()) @@ -75,7 +81,7 @@ export class Backend { * @returns {CSL.Address} */ public async walletUnusedAddress(email: string): Promise { - const { data } = await axios.post(`${this.url}/v0/wallet/extra-address`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/extra-address`, { 'email': email }, this.headers()) @@ -100,7 +106,7 @@ export class Backend { const payload = serialize(requestData) - const { data } = await axios.post(`${this.url}/v0/wallet/activate`, payload, + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/activate`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -134,7 +140,7 @@ export class Backend { const payload = serialize(requestData) - const { data } = await axios.post(`${this.url}/v0/wallet/activate-and-send-funds`, payload, + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/activate-and-send-funds`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -166,7 +172,29 @@ export class Backend { const payload = serialize(requestData) - const { data } = await axios.post(`${this.url}/v0/wallet/send-funds`, payload, + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/send-funds`, payload, + this.headers({ 'Content-Type': 'application/json' }) + ) + + const response: SendFundsResponse = { + transaction: data.transaction, + transaction_fee: data.transaction_fee, + transaction_id: data.transaction_id + } + + return response + } + + public async sendFundsv1(jwt: string, outs: Output[], proof: SigmaProof): Promise { + const requestData = { + 'jwt': jwt, + 'outs': outs, + 'proof': proof, + } + + const payload = serialize(requestData) + + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/send-funds`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -188,7 +216,7 @@ export class Backend { async prepareTx(params: PrepareTxParameters): Promise { const payload = serialize(params) - const { data } = await axios.post(`${this.url}/v0/wallet/prepare-tx`, payload, + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/prepare-tx`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -209,7 +237,7 @@ export class Backend { * @returns {SubmitTxResult} - Transaction ID and email delivery errors, if any */ public async submitTx(transaction: string, email_recipients: string[] = [], sender?: string): Promise { - const { data } = await axios.post(`${this.url}/v0/tx/submit`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/tx/submit`, { email_recipients: email_recipients, sender: sender, transaction: transaction @@ -230,7 +258,7 @@ export class Backend { * @returns {SubmitTxResult} - Transaction ID and email delivery errors, if any */ public async addVkeyAndSubmitTx(unsigned_transaction: string, vkey_witness: string, email_recipients: string[] = [], sender?: string): Promise { - const { data } = await axios.post(`${this.url}/v0/tx/add-vkey-and-submit`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/tx/add-vkey-and-submit`, { unsigned_transaction: unsigned_transaction, vkey_witness: vkey_witness, email_recipients: email_recipients, @@ -250,7 +278,7 @@ export class Backend { * @returns {UTxO[]} */ public async addressUtxo(address: CSL.Address): Promise { - const { data } = await axios.post(`${this.url}/v0/address/utxos`, [address.to_bech32()], this.headers()) + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/address/utxos`, [address.to_bech32()], this.headers()) const result: UTxO[] = [] @@ -286,7 +314,7 @@ export class Backend { * @returns {BalanceResponse} */ public async balance(email: string): Promise { - const { data } = await axios.post(`${this.url}/v0/address/balance`, email, this.headers({ 'Content-Type': 'application/json' })) + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/address/balance`, email, this.headers({ 'Content-Type': 'application/json' })) return data } @@ -297,7 +325,7 @@ export class Backend { * @returns {Transaction[]} */ public async txHistory(email: string): Promise { - const { data } = await axios.post(`${this.url}/v0/wallet/txs`, { 'email': email }, this.headers()) + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/txs`, { 'email': email }, this.headers()) // TODO: fetch token tickers from Cardano Token Registry if it isn't done on the back end // diff --git a/src/Service/Google.ts b/src/Service/Google.ts index c2e8931..b232472 100644 --- a/src/Service/Google.ts +++ b/src/Service/Google.ts @@ -1,5 +1,6 @@ import axios from 'axios' import { GoogleTokenResponse, GoogleCertKey } from '../Types' +import { base64UrlDecode } from '../Utils' export class GoogleApi { private clientId: string @@ -130,4 +131,14 @@ export class GoogleApi { const parts = jwt.split(".") return `${parts[0]}.${parts[1]}` } -} \ No newline at end of file + + /** + * Base64url-decode the jwt + * @param {string} jwt - The JWT string. + * @returns {string} The decoded JWT without the signature. + */ + public decodeJwt(jwt: string): string { + const parts = jwt.split(".") + return `${base64UrlDecode(parts[0])}.${base64UrlDecode(parts[1])}` + } +} diff --git a/src/Service/Prover.ts b/src/Service/Prover.ts index 822d3fc..35f680f 100644 --- a/src/Service/Prover.ts +++ b/src/Service/Prover.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import forge from 'node-forge'; -import { ProofBytes, ProverPublicKey, ProofInput, BigIntWrap } from '../Types'; +import { ProofBytes, ProverPublicKey, PlonkProofInput, BigIntWrap } from '../Types'; import { deserialize, serialize } from '../JSON'; /** @@ -41,10 +41,10 @@ export class Prover { /** * Submit a proof request to the Prover. It will return a Request ID which can be used to retrieve proof status * @async - * @param {ProofInput} proofInput for the expMod circuit: exponent, modulus, signature and token name + * @param {PlonkProofInput} proofInput for the expMod circuit: exponent, modulus, signature and token name * @returns {string} proof request ID */ - public async requestProof(proofInput: ProofInput): Promise { + public async requestProof(proofInput: PlonkProofInput): Promise { const keys = await this.serverKeys() const key = keys[0] @@ -108,10 +108,10 @@ export class Prover { /** * Obtain a Proof from the Prover. Unlike requestProof(), this method waits for the proof completion * @async - * @param {ProofInput} proofInput for the expMod circuit: exponent, modulus, signature and token name + * @param {PlonkProofInput} proofInput for the expMod circuit: exponent, modulus, signature and token name * @returns {ProofBytes} ZK proof bytes for the expMod circuit */ - public async prove(proofInput: ProofInput): Promise { + public async prove(proofInput: PlonkProofInput): Promise { const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) const proofId = await this.requestProof(proofInput) diff --git a/src/Types/Prover.ts b/src/Types/Prover.ts index 63c48dd..75a7bdc 100644 --- a/src/Types/Prover.ts +++ b/src/Types/Prover.ts @@ -67,9 +67,33 @@ export interface ProverPublicKey { * @property {BigIntWrap} piSignature - Signature attached to the Google OAuth JSON Web Token * @property {BigIntWrap} piTokenName - The name of the token minted in the wallet initialisation transaction */ -export interface ProofInput { +export interface PlonkProofInput { piPubE: BigIntWrap piPubN: BigIntWrap piSignature: BigIntWrap piTokenName: BigIntWrap } + +/** + * Sigma protocols proof input + * + * @property {BigIntWrap} piPubE - Google's RSA public exponent + * @property {BigIntWrap} piPubN - Google's RSA public modulus + * @property {BigIntWrap} piSignature - Signature attached to the Google OAuth JSON Web Token + */ +export interface SigmaProofInput { + piPubE: BigIntWrap + piPubN: BigIntWrap + piSignature: BigIntWrap +} + +/** + * Sigma protocols proof + * + * @property {BigIntWrap[]} v - Verifier's shares + * @property {BigIntWrap[]} aut - Authentication elements + */ +export interface SigmaProof { + v: BigIntWrap[] + auts: BigIntWrap[] +} diff --git a/src/Utils.ts b/src/Utils.ts index 9db18e4..c180949 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -21,9 +21,17 @@ export function bytesToHex(bytes: Uint8Array): string { /** * Convert BigInt to byte array */ -export function bigIntToBytes(bigInt: bigint): Uint8Array { +export function bigIntToBytes(bigInt: bigint, numBytes: number | undefined = undefined): Uint8Array { const hex = bigInt.toString(16) - const paddedHex = hex.length % 2 ? '0' + hex : hex + let paddedHex = hex.length % 2 ? '0' + hex : hex + if (numBytes) { + const currentBytes = paddedHex.length / 2 + if (numBytes > currentBytes) { + for (let i = 0; i < (numBytes - currentBytes) / 2; ++i) { + paddedHex = '00' + paddedHex + } + } + } const bytes = new Uint8Array(paddedHex.length / 2) for (let i = 0; i < paddedHex.length; i += 2) { bytes[i / 2] = parseInt(paddedHex.substr(i, 2), 16) @@ -39,6 +47,16 @@ export function bytesToBase64Url(bytes: Uint8Array): string { return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') } +export function base64UrlDecode(str: string): string { + const base64Encoded = str.replace(/-/g, '+').replace(/_/g, '/'); + const padding = str.length % 4 === 0 ? '' : '='.repeat(4 - (str.length % 4)); + const base64WithPadding = base64Encoded + padding; + return atob(base64WithPadding) + .split('') + .map(char => String.fromCharCode(char.charCodeAt(0))) + .join(''); +} + export function harden(num: number): number { return 0x80000000 + num } @@ -56,3 +74,19 @@ export function b64ToBn(b64: string): BigIntWrap { return new BigIntWrap(BigInt('0x' + hex.join(''))) } + + +// binary exponentiation with remainder calculation on each step to prevent memory blowup +export function expMod(base: bigint, exponent: bigint, modulus: bigint): bigint { + if (modulus === 1n) return 0n; + let result = 1n; + base = base % modulus; + while (exponent > 0n) { + if ((exponent & 1n) === 1n) { + result = (result * base) % modulus; + } + exponent >>= 1n; + base = (base * base) % modulus; + } + return result; +} diff --git a/src/Wallet.ts b/src/Wallet.ts index 123fd73..19e0999 100644 --- a/src/Wallet.ts +++ b/src/Wallet.ts @@ -1,8 +1,9 @@ +import forge from 'node-forge'; import * as CSL from '@emurgo/cardano-serialization-lib-browser' import { Backend } from './Service/Backend' -import { UTxO, Output, BigIntWrap, SubmitTxResult, ProofBytes, AddressType, TransactionRequest, ProofInput, SmartTxRecipient, BalanceResponse, Transaction, PrepareTxParameters, PrepareTxResponse } from './Types' +import { UTxO, Output, BigIntWrap, SubmitTxResult, ProofBytes, AddressType, TransactionRequest, PlonkProofInput, SigmaProofInput, SigmaProof, SmartTxRecipient, BalanceResponse, Transaction, PrepareTxParameters, PrepareTxResponse } from './Types' import { Prover } from './Service/Prover' -import { b64ToBn, harden, hexToBytes } from './Utils' +import { bytesToBase64Url, bigIntToBytes, b64ToBn, harden, hexToBytes, expMod } from './Utils' import { Storage } from './Service/Storage' import { Session } from './Service/Session' import { GoogleApi } from './Service/Google' @@ -15,26 +16,48 @@ export class Wallet extends EventTarget { private tokenSKey?: CSL.Bip32PrivateKey private userId?: string private activated: boolean = false - private proof: ProofBytes | null = null + private proof: ProofBytes | SigmaProof | null = null private storage: Storage private session: Session - private googleApi: GoogleApi - private backend: Backend - private prover: Prover + private googleApi?: GoogleApi + private backend?: Backend + private prover?: Prover + private apiVersion: number = 0 + + // Disallow instantiating Wallet via constructor + private constructor() { + super() + this.storage = new Storage() + this.session = new Session() + } /** * @param {Backend} backend - A Backend object for interaction with the backend * @param {Prover} prover - A Prover object for interaction with the prover * @param {GoogleApi} googleApi - A GoogleApi object for interaction with Google OAuth */ - constructor(backend: Backend, prover: Prover, googleApi: GoogleApi) { - super() - this.storage = new Storage() - this.session = new Session() - this.googleApi = googleApi - this.backend = backend - this.prover = prover + public static withV0Api(backend: Backend, prover: Prover, googleApi: GoogleApi) { + const wallet = new Wallet() + wallet.googleApi = googleApi + wallet.backend = backend + wallet.prover = prover + wallet.apiVersion = 0 + wallet.backend.setApiVersion(wallet.apiVersion) + return wallet + } + + /** + * @param {Backend} backend - A Backend object for interaction with the backend + * @param {GoogleApi} googleApi - A GoogleApi object for interaction with Google OAuth + */ + public static withV1Api(backend: Backend, googleApi: GoogleApi) { + const wallet = new Wallet() + wallet.googleApi = googleApi + wallet.backend = backend + wallet.apiVersion = 1 + wallet.backend.setApiVersion(wallet.apiVersion) + return wallet } public login(): void { @@ -45,7 +68,7 @@ export class Wallet extends EventTarget { this.session.saveState(state) // Redirect to Google OAuth - const authUrl = this.googleApi.getAuthUrl(state) + const authUrl = this.googleApi!.getAuthUrl(state) window.location.href = authUrl } @@ -54,7 +77,12 @@ export class Wallet extends EventTarget { } public isLoggedIn(): boolean { - return this.jwt !== undefined && this.tokenSKey !== undefined && this.userId !== undefined + if (this.apiVersion === 0) { + return this.jwt !== undefined && this.tokenSKey !== undefined && this.userId !== undefined + } else if (this.apiVersion === 1) { + return this.jwt !== undefined && this.userId !== undefined + } + return false } public hasProof(): boolean { @@ -99,13 +127,13 @@ export class Wallet extends EventTarget { } // Get JWT token - const jwt = await this.googleApi.getJWTFromCode(code) + const jwt = await this.googleApi!.getJWTFromCode(code) if (!jwt) { throw new Error('Failed to get JWT from authorization code') } // Set user ID - this.userId = this.googleApi.getUserId(jwt) + this.userId = this.googleApi!.getUserId(jwt) // Get Cardano address const address = await this.addressForGmail(this.userId).then((x: any) => x.to_bech32()) @@ -137,30 +165,118 @@ export class Wallet extends EventTarget { } private async getProof(): Promise { - if (!this.jwt || !this.tokenSKey) { + if (!this.jwt) { throw new Error('Wallet is not initialised') } - const pubkeyHex = this.tokenSKey.to_public().to_raw_key().hash().to_hex() - const keyId = this.googleApi.getKeyId(this.jwt) - const matchingKey = await this.googleApi.getMatchingKey(keyId) + const keyId = this.googleApi!.getKeyId(this.jwt) + const matchingKey = await this.googleApi!.getMatchingKey(keyId) if (!matchingKey) { throw new Error(`Failed to find matching Google cert for key ${keyId}`) } - const signature = this.googleApi.getSignature(this.jwt) - const empi: ProofInput = { - piPubE: b64ToBn(matchingKey.e), - piPubN: b64ToBn(matchingKey.n), - piSignature: b64ToBn(signature), - piTokenName: new BigIntWrap("0x" + pubkeyHex) - } + const signature = this.googleApi!.getSignature(this.jwt) + if (this.apiVersion == 0 && this.prover) { + if (!this.tokenSKey) { + throw new Error('Wallet is not initialised') + } + + const pubkeyHex = this.tokenSKey.to_public().to_raw_key().hash().to_hex() + + const empi: PlonkProofInput = { + piPubE: b64ToBn(matchingKey.e), + piPubN: b64ToBn(matchingKey.n), + piSignature: b64ToBn(signature), + piTokenName: new BigIntWrap("0x" + pubkeyHex) + } - this.jwt = this.googleApi.stripSignature(this.jwt) - this.proof = await this.prover.prove(empi) + this.jwt = this.googleApi!.stripSignature(this.jwt) + this.proof = await this.prover.prove(empi) + } else if (this.apiVersion == 1) { + const sigmaProofInput: SigmaProofInput = { + piPubE: b64ToBn(matchingKey.e), + piPubN: b64ToBn(matchingKey.n), + piSignature: b64ToBn(signature), + } + + this.proof = this.sigmaProve(sigmaProofInput) + } this.dispatchEvent(new CustomEvent('proof_computed')) } + private digest(data: bigint[], mod: bigint): bigint { + const arrays = data.map((x) => bigIntToBytes(x, 256)) + + arrays.forEach((item) => console.log(item)) + + // Get the total length of all arrays. + const length = 256 * data.length + + // Create a new array with total length and merge all source arrays. + let mergedArray = new Uint8Array(length); + + let offset = 0; + + arrays.forEach(item => { + mergedArray.set(item, offset); + offset += item.length; + }); + + console.log(mergedArray) + const bs = forge.util.createBuffer(mergedArray).getBytes() + console.log(bs.length) + + const md = forge.md.sha256.create(); + md.update(bs); + const result = md.digest().toHex() + console.log(result) + return BigInt('0x' + result) % mod + } + + private sigmaProve(input: SigmaProofInput): SigmaProof { + const iterations = 2 + + const n = input.piPubN.toBigInt() + const s = input.piSignature.toBigInt() + const e = input.piPubE.toBigInt() + const c = expMod(s, e, n) + + console.log(c) + + + const auts = [] + const v = [] + + + + for (let iter = 0; iter < iterations; ++iter) { + // Share-in-Mind(s): picks a random element a ∈ Z_N, + // defines a function f (x) = a · s^x mod N , + // sets SHcpt = a, computes aut = a^e mod N , + // outputs (SHcpt, aut); + const bytes = Uint8Array.from(forge.random.getBytesSync(256).split("").map(x => x.charCodeAt(0))) // 256 bytes = 2048 bits, size of the keys + const a = b64ToBn(btoa(String.fromCharCode(...bytes))).toBigInt() % n + const aut = expMod(a, e, n) + + const i = this.digest([c, aut], e) // Fiat-Shamir transform -- use digest instead of a random element + console.log(`I == ${i}`) + + //Distribute(s, SHcpt, i): parses SHcpt = a, + //computes si = a · s^i mod N , + //outputs vi = si. + + const vi = (a * expMod(s, i, n)) % n + + auts.push(new BigIntWrap(aut)) + v.push(new BigIntWrap(vi)) + } + + return { + v: v, + auts: auts, + } as SigmaProof + } + public getUserId(): string { if (!this.userId) { throw new Error('Wallet is not initialised') @@ -173,7 +289,7 @@ export class Wallet extends EventTarget { * Get the Cardano address for a gmail address */ public async addressForGmail(gmail: string): Promise { - return await this.backend.walletMainAddress(gmail) + return await this.backend!.walletMainAddress(gmail) } @@ -196,7 +312,7 @@ export class Wallet extends EventTarget { if (!this.userId) { throw new Error('Wallet is not initialised') } - return await this.backend.walletUnusedAddress(this.userId) + return await this.backend!.walletUnusedAddress(this.userId) } /** @@ -207,7 +323,7 @@ export class Wallet extends EventTarget { if (!this.userId) { throw new Error('Wallet is not initialised') } - const balance = await this.backend.balance(this.userId) + const balance = await this.backend!.balance(this.userId) return balance } @@ -228,7 +344,7 @@ export class Wallet extends EventTarget { if (!this.userId) { throw new Error('Wallet is not initialised') } - return await this.backend.txHistory(this.userId) + return await this.backend!.txHistory(this.userId) } /** @@ -246,7 +362,7 @@ export class Wallet extends EventTarget { const address = await this.getAddress() let utxos: UTxO[] = [] try { - utxos = await this.backend.addressUtxo(address) + utxos = await this.backend!.addressUtxo(address) } catch (err) { console.log("getUtxos()") console.log(err) @@ -388,7 +504,7 @@ export class Wallet extends EventTarget { private async checkTransactionStatus(txId: string, recipient: string): Promise { try { const address = CSL.Address.from_bech32(recipient) - const utxos = await this.backend.addressUtxo(address) + const utxos = await this.backend!.addressUtxo(address) for (const utxo of utxos) { if ((utxo as any).ref.transaction_id === txId) { @@ -420,7 +536,7 @@ export class Wallet extends EventTarget { transaction: transaction, } - return await this.backend.prepareTx(params) + return await this.backend!.prepareTx(params) } private async sendTo(rec: SmartTxRecipient): Promise { @@ -461,8 +577,9 @@ export class Wallet extends EventTarget { let txHex const outs: Output[] = [{ address: recipientAddress.to_bech32(), value: rec.assets }] - if (this.activated) { - const resp = await this.backend.sendFunds(this.userId, outs, this.tokenSKey.to_public().to_raw_key().hash().to_hex()) + if (this.activated || this.apiVersion == 1) { + //const resp = await this.backend!.sendFunds(this.userId, outs, this.tokenSKey.to_public().to_raw_key().hash().to_hex()) + const resp = await this.backend!.sendFundsv1(this.googleApi!.decodeJwt(this.jwt), outs, this.proof as SigmaProof) txHex = resp.transaction } else { const pubkeyHex = this.tokenSKey.to_public().to_raw_key().hash().to_hex() @@ -473,7 +590,7 @@ export class Wallet extends EventTarget { while (!this.hasProof()) { await delay(5_000) } - const resp = await this.backend.activateAndSendFunds(header + '.' + payload, pubkeyHex, this.proof as ProofBytes, outs) + const resp = await this.backend!.activateAndSendFunds(header + '.' + payload, pubkeyHex, this.proof as ProofBytes, outs) txHex = resp.transaction } @@ -481,7 +598,7 @@ export class Wallet extends EventTarget { transaction.sign_and_add_vkey_signature(this.tokenSKey.to_raw_key()) const signedTxHex = Array.from(new Uint8Array(transaction.to_bytes())).map(b => b.toString(16).padStart(2, '0')).join('') - const submitTxResult = await this.backend.submitTx(signedTxHex, emailRecipients, this.userId) + const submitTxResult = await this.backend!.submitTx(signedTxHex, emailRecipients, this.userId) this.activated = true return submitTxResult