From a0c7ca4acd4dbec68c8211d229f9bc7747b86e4a Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 6 Jan 2026 22:20:01 +1000 Subject: [PATCH 1/5] WIP prover --- src/Types/Prover.ts | 24 +++++++++ src/Utils.ts | 16 ++++++ src/Wallet.ts | 116 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 136 insertions(+), 20 deletions(-) diff --git a/src/Types/Prover.ts b/src/Types/Prover.ts index 63c48dd..1a6ed8b 100644 --- a/src/Types/Prover.ts +++ b/src/Types/Prover.ts @@ -73,3 +73,27 @@ export interface ProofInput { 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[]} vi - Verifier's shares + * @property {BigIntWrap} aut - Authentication element + */ +export interface SigmaProof { + vi: BigIntWrap + aut: BigIntWrap +} diff --git a/src/Utils.ts b/src/Utils.ts index 9db18e4..59866b5 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -56,3 +56,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..d61d710 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, b64ToBn, harden, hexToBytes, expMod } from './Utils' import { Storage } from './Service/Storage' import { Session } from './Service/Session' import { GoogleApi } from './Service/Google' @@ -21,20 +22,42 @@ export class Wallet extends EventTarget { private session: Session private googleApi: GoogleApi private backend: Backend - private prover: Prover + private prover?: Prover + private apiVersion: number + + // Disallow instantiating Wallet via constructor + private constructor() { + super() + } /** * @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.storage = new Storage() + wallet.session = new Session() + wallet.googleApi = googleApi + wallet.backend = backend + wallet.prover = prover + wallet.apiVersion = 0 + 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.storage = new Storage() + wallet.session = new Session() + wallet.googleApi = googleApi + wallet.backend = backend + wallet.apiVersion = 1 + return wallet } public login(): void { @@ -137,30 +160,83 @@ 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) 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) - } + 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) + } else if (this.apiVersion == 1) { + const sigmaProofInput: SigmaProofInput = { + piPubE: b64ToBn(matchingKey.e), + piPubN: b64ToBn(matchingKey.n), + piSignature: b64ToBn(signature), + } - this.jwt = this.googleApi.stripSignature(this.jwt) - this.proof = await this.prover.prove(empi) + this.proof = this.sigmaProve(empi) + } this.dispatchEvent(new CustomEvent('proof_computed')) } + private digest(data: bigint[], mod: bigint): bigint { + let s = '' + for (let i = 0; i < data.length; i++) { + s += data[i] + } + + const md = forge.md.sha256.create(); + md.update(s); + return BigInt('0x' + md.digest().toHex()) % mod + } + + private sigmaProve(input: SigmaProofInput): SigmaProof { + const n = input.piPubN.toBigInt() + const s = input.piSignature.toBigInt() + const e = input.piPubE.toBigInt() + const c = expMod(s, e, n) + + // 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 a = b64ToBn(bytesToBase64Url(forge.random.getBytesSync(256))).toBigInt() % n // 256 bytes = 2048 bits, size of the keys + const aut = expMod(a, e, n) + + const i = this.digest([c, aut], e) // Fiat-Shamir transform -- use digest instead of a random element + + //Distribute(s, SHcpt, i): parses SHcpt = a, + //computes si = a · s^i mod N , + //outputs vi = si. + + const v = [(a * expMod(s, i, n)) % n] + + return { + vi: v, + aut: aut, + } as SigmaProof + } + public getUserId(): string { if (!this.userId) { throw new Error('Wallet is not initialised') From a2721e8439801762a8c9ec2eea59e70a7e1af6d3 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 7 Jan 2026 18:31:52 +1000 Subject: [PATCH 2/5] More or less stable version --- package.json | 2 +- src/Service/Backend.ts | 32 ++++++++------ src/Service/Prover.ts | 10 ++--- src/Types/Prover.ts | 10 ++--- src/Wallet.ts | 99 +++++++++++++++++++++++++----------------- 5 files changed, 89 insertions(+), 64 deletions(-) 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..01b1d86 100644 --- a/src/Service/Backend.ts +++ b/src/Service/Backend.ts @@ -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}/${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}/${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}/${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}/${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}/${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}/${this.apiVersion}/wallet/activate-and-send-funds`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -166,7 +172,7 @@ 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}/${this.apiVersion}/wallet/send-funds`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -188,7 +194,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}/${this.apiVersion}/wallet/prepare-tx`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -209,7 +215,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}/${this.apiVersion}/tx/submit`, { email_recipients: email_recipients, sender: sender, transaction: transaction @@ -230,7 +236,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}/${this.apiVersion}/tx/add-vkey-and-submit`, { unsigned_transaction: unsigned_transaction, vkey_witness: vkey_witness, email_recipients: email_recipients, @@ -250,7 +256,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}/${this.apiVersion}/address/utxos`, [address.to_bech32()], this.headers()) const result: UTxO[] = [] @@ -286,7 +292,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}/${this.apiVersion}/address/balance`, email, this.headers({ 'Content-Type': 'application/json' })) return data } @@ -297,7 +303,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}/${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/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 1a6ed8b..b3a654b 100644 --- a/src/Types/Prover.ts +++ b/src/Types/Prover.ts @@ -67,7 +67,7 @@ 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 @@ -90,10 +90,10 @@ export interface SigmaProofInput { /** * Sigma protocols proof * - * @property {BigIntWrap[]} vi - Verifier's shares - * @property {BigIntWrap} aut - Authentication element + * @property {BigIntWrap[]} v - Verifier's shares + * @property {BigIntWrap[]} aut - Authentication elements */ export interface SigmaProof { - vi: BigIntWrap - aut: BigIntWrap + v: BigIntWrap[] + aut: BigIntWrap[] } diff --git a/src/Wallet.ts b/src/Wallet.ts index d61d710..3b3eeaf 100644 --- a/src/Wallet.ts +++ b/src/Wallet.ts @@ -16,18 +16,20 @@ 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 googleApi?: GoogleApi + private backend?: Backend private prover?: Prover - private apiVersion: number + private apiVersion: number = 0 // Disallow instantiating Wallet via constructor private constructor() { super() + this.storage = new Storage() + this.session = new Session() } /** @@ -37,12 +39,11 @@ export class Wallet extends EventTarget { */ public static withV0Api(backend: Backend, prover: Prover, googleApi: GoogleApi) { const wallet = new Wallet() - wallet.storage = new Storage() - wallet.session = new Session() wallet.googleApi = googleApi wallet.backend = backend wallet.prover = prover wallet.apiVersion = 0 + wallet.backend.setApiVersion(wallet.apiVersion) return wallet } @@ -52,11 +53,10 @@ export class Wallet extends EventTarget { */ public static withV1Api(backend: Backend, googleApi: GoogleApi) { const wallet = new Wallet() - wallet.storage = new Storage() - wallet.session = new Session() wallet.googleApi = googleApi wallet.backend = backend wallet.apiVersion = 1 + wallet.backend.setApiVersion(wallet.apiVersion) return wallet } @@ -68,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 } @@ -77,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 { @@ -122,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()) @@ -164,12 +169,12 @@ export class Wallet extends EventTarget { throw new Error('Wallet is not initialised') } - 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 signature = this.googleApi!.getSignature(this.jwt) if (this.apiVersion == 0 && this.prover) { if (!this.tokenSKey) { throw new Error('Wallet is not initialised') @@ -184,7 +189,7 @@ export class Wallet extends EventTarget { piTokenName: new BigIntWrap("0x" + pubkeyHex) } - this.jwt = this.googleApi.stripSignature(this.jwt) + this.jwt = this.googleApi!.stripSignature(this.jwt) this.proof = await this.prover.prove(empi) } else if (this.apiVersion == 1) { const sigmaProofInput: SigmaProofInput = { @@ -193,7 +198,7 @@ export class Wallet extends EventTarget { piSignature: b64ToBn(signature), } - this.proof = this.sigmaProve(empi) + this.proof = this.sigmaProve(sigmaProofInput) } this.dispatchEvent(new CustomEvent('proof_computed')) @@ -211,29 +216,43 @@ export class Wallet extends EventTarget { } private sigmaProve(input: SigmaProofInput): SigmaProof { + const iterations = 16 + const n = input.piPubN.toBigInt() const s = input.piSignature.toBigInt() const e = input.piPubE.toBigInt() const c = expMod(s, e, n) - // 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 a = b64ToBn(bytesToBase64Url(forge.random.getBytesSync(256))).toBigInt() % n // 256 bytes = 2048 bits, size of the keys - const aut = expMod(a, e, n) - const i = this.digest([c, aut], e) // Fiat-Shamir transform -- use digest instead of a random element + const auts = [] + const v = [] + + - //Distribute(s, SHcpt, i): parses SHcpt = a, - //computes si = a · s^i mod N , - //outputs vi = si. + 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(bytesToBase64Url(bytes)).toBigInt() % n + const aut = expMod(a, e, n) - const v = [(a * expMod(s, i, n)) % n] + const i = this.digest([c, aut], e) // Fiat-Shamir transform -- use digest instead of a random element + + //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 { - vi: v, - aut: aut, + v: v, + aut: auts, } as SigmaProof } @@ -249,7 +268,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) } @@ -272,7 +291,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) } /** @@ -283,7 +302,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 } @@ -304,7 +323,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) } /** @@ -322,7 +341,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) @@ -464,7 +483,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) { @@ -496,7 +515,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 { @@ -538,7 +557,7 @@ export class Wallet extends EventTarget { 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()) + const resp = await this.backend!.sendFunds(this.userId, outs, this.tokenSKey.to_public().to_raw_key().hash().to_hex()) txHex = resp.transaction } else { const pubkeyHex = this.tokenSKey.to_public().to_raw_key().hash().to_hex() @@ -549,7 +568,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 } @@ -557,7 +576,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 From 1cdfd842389b572e1167a5a6aa741855cfa1b3b2 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 22 Jan 2026 20:59:20 +1000 Subject: [PATCH 3/5] Update prover --- src/Wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.ts b/src/Wallet.ts index 3b3eeaf..c6d740b 100644 --- a/src/Wallet.ts +++ b/src/Wallet.ts @@ -238,7 +238,7 @@ export class Wallet extends EventTarget { const a = b64ToBn(bytesToBase64Url(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 + const i = this.digest([c.toString(), aut.toString()], e) // Fiat-Shamir transform -- use digest instead of a random element //Distribute(s, SHcpt, i): parses SHcpt = a, //computes si = a · s^i mod N , From 49f42c1151530b7e372a6d4edbdb1a30b2d5870d Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 23 Jan 2026 00:24:39 +1000 Subject: [PATCH 4/5] Fixes --- package-lock.json | 75 +++++++++++++++++++++--------------------- src/Service/Backend.ts | 50 ++++++++++++++++++++-------- src/Service/Google.ts | 13 +++++++- src/Types/Prover.ts | 4 +-- src/Utils.ts | 10 ++++++ src/Wallet.ts | 13 ++++---- 6 files changed, 105 insertions(+), 60 deletions(-) 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/src/Service/Backend.ts b/src/Service/Backend.ts index 01b1d86..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. @@ -46,7 +46,7 @@ export class Backend { * @returns {Settings} */ public async settings(): Promise { - const { data } = await axios.get(`${this.url}/${this.apiVersion}/settings`, this.headers()) + const { data } = await axios.get(`${this.url}/v${this.apiVersion}/settings`, this.headers()) return data } @@ -56,7 +56,7 @@ export class Backend { * @returns {ClientCredentials} */ public async credentials(): Promise { - const { data } = await axios.get(`${this.url}/${this.apiVersion}/oauth/credentials`, this.headers()) + const { data } = await axios.get(`${this.url}/v${this.apiVersion}/oauth/credentials`, this.headers()) return data } @@ -67,7 +67,7 @@ export class Backend { * @returns {CSL.Address} */ public async walletMainAddress(email: string): Promise { - const { data } = await axios.post(`${this.url}/${this.apiVersion}/wallet/address`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/address`, { 'email': email }, this.headers()) @@ -81,7 +81,7 @@ export class Backend { * @returns {CSL.Address} */ public async walletUnusedAddress(email: string): Promise { - const { data } = await axios.post(`${this.url}/${this.apiVersion}/wallet/extra-address`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/extra-address`, { 'email': email }, this.headers()) @@ -106,7 +106,7 @@ export class Backend { const payload = serialize(requestData) - const { data } = await axios.post(`${this.url}/${this.apiVersion}/wallet/activate`, payload, + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/activate`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -140,7 +140,7 @@ export class Backend { const payload = serialize(requestData) - const { data } = await axios.post(`${this.url}/${this.apiVersion}/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' }) ) @@ -172,7 +172,29 @@ export class Backend { const payload = serialize(requestData) - const { data } = await axios.post(`${this.url}/${this.apiVersion}/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' }) ) @@ -194,7 +216,7 @@ export class Backend { async prepareTx(params: PrepareTxParameters): Promise { const payload = serialize(params) - const { data } = await axios.post(`${this.url}/${this.apiVersion}/wallet/prepare-tx`, payload, + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/wallet/prepare-tx`, payload, this.headers({ 'Content-Type': 'application/json' }) ) @@ -215,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}/${this.apiVersion}/tx/submit`, { + const { data } = await axios.post(`${this.url}/v${this.apiVersion}/tx/submit`, { email_recipients: email_recipients, sender: sender, transaction: transaction @@ -236,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}/${this.apiVersion}/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, @@ -256,7 +278,7 @@ export class Backend { * @returns {UTxO[]} */ public async addressUtxo(address: CSL.Address): Promise { - const { data } = await axios.post(`${this.url}/${this.apiVersion}/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[] = [] @@ -292,7 +314,7 @@ export class Backend { * @returns {BalanceResponse} */ public async balance(email: string): Promise { - const { data } = await axios.post(`${this.url}/${this.apiVersion}/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 } @@ -303,7 +325,7 @@ export class Backend { * @returns {Transaction[]} */ public async txHistory(email: string): Promise { - const { data } = await axios.post(`${this.url}/${this.apiVersion}/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/Types/Prover.ts b/src/Types/Prover.ts index b3a654b..75a7bdc 100644 --- a/src/Types/Prover.ts +++ b/src/Types/Prover.ts @@ -94,6 +94,6 @@ export interface SigmaProofInput { * @property {BigIntWrap[]} aut - Authentication elements */ export interface SigmaProof { - v: BigIntWrap[] - aut: BigIntWrap[] + v: BigIntWrap[] + auts: BigIntWrap[] } diff --git a/src/Utils.ts b/src/Utils.ts index 59866b5..05d720b 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -39,6 +39,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 } diff --git a/src/Wallet.ts b/src/Wallet.ts index c6d740b..87712df 100644 --- a/src/Wallet.ts +++ b/src/Wallet.ts @@ -207,7 +207,7 @@ export class Wallet extends EventTarget { private digest(data: bigint[], mod: bigint): bigint { let s = '' for (let i = 0; i < data.length; i++) { - s += data[i] + s += data[i].toString() } const md = forge.md.sha256.create(); @@ -235,10 +235,10 @@ export class Wallet extends EventTarget { // 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(bytesToBase64Url(bytes)).toBigInt() % n + const a = b64ToBn(btoa(String.fromCharCode(...bytes))).toBigInt() % n const aut = expMod(a, e, n) - const i = this.digest([c.toString(), aut.toString()], e) // Fiat-Shamir transform -- use digest instead of a random element + const i = this.digest([c, aut], e) // Fiat-Shamir transform -- use digest instead of a random element //Distribute(s, SHcpt, i): parses SHcpt = a, //computes si = a · s^i mod N , @@ -252,7 +252,7 @@ export class Wallet extends EventTarget { return { v: v, - aut: auts, + auts: auts, } as SigmaProof } @@ -556,8 +556,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() From 7c7d3659b054f6af532e1683e4f177e73fc319fb Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 29 Jan 2026 22:03:42 +1000 Subject: [PATCH 5/5] Correct proving --- src/Utils.ts | 12 ++++++++++-- src/Wallet.ts | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/Utils.ts b/src/Utils.ts index 05d720b..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) diff --git a/src/Wallet.ts b/src/Wallet.ts index 87712df..19e0999 100644 --- a/src/Wallet.ts +++ b/src/Wallet.ts @@ -3,7 +3,7 @@ import * as CSL from '@emurgo/cardano-serialization-lib-browser' import { Backend } from './Service/Backend' import { UTxO, Output, BigIntWrap, SubmitTxResult, ProofBytes, AddressType, TransactionRequest, PlonkProofInput, SigmaProofInput, SigmaProof, SmartTxRecipient, BalanceResponse, Transaction, PrepareTxParameters, PrepareTxResponse } from './Types' import { Prover } from './Service/Prover' -import { bytesToBase64Url, b64ToBn, harden, hexToBytes, expMod } 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' @@ -205,23 +205,43 @@ export class Wallet extends EventTarget { } private digest(data: bigint[], mod: bigint): bigint { - let s = '' - for (let i = 0; i < data.length; i++) { - s += data[i].toString() - } + 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(s); - return BigInt('0x' + md.digest().toHex()) % mod + md.update(bs); + const result = md.digest().toHex() + console.log(result) + return BigInt('0x' + result) % mod } private sigmaProve(input: SigmaProofInput): SigmaProof { - const iterations = 16 + 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 = [] @@ -239,6 +259,7 @@ export class Wallet extends EventTarget { 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 ,