diff options
Diffstat (limited to 'lib/wallet')
-rw-r--r-- | lib/wallet/cryptoApi.ts | 16 | ||||
-rw-r--r-- | lib/wallet/cryptoLib.ts | 67 | ||||
-rw-r--r-- | lib/wallet/emscriptif.ts | 121 | ||||
-rw-r--r-- | lib/wallet/types.ts | 14 | ||||
-rw-r--r-- | lib/wallet/wallet.ts | 72 |
5 files changed, 219 insertions, 71 deletions
diff --git a/lib/wallet/cryptoApi.ts b/lib/wallet/cryptoApi.ts index 88b82ae3b..40be13732 100644 --- a/lib/wallet/cryptoApi.ts +++ b/lib/wallet/cryptoApi.ts @@ -114,7 +114,8 @@ export class CryptoApi { handleWorkerError(ws: WorkerState, e: ErrorEvent) { if (ws.currentWorkItem) { - console.error(`error in worker during ${ws.currentWorkItem!.operation}`, e); + console.error(`error in worker during ${ws.currentWorkItem!.operation}`, + e); } else { console.error("error in worker", e); } @@ -243,16 +244,17 @@ export class CryptoApi { return this.doRpc("rsaUnblind", 4, sig, bk, pk); } - createWithdrawSession(kappa: number, meltCoin: Coin, - newCoinDenoms: Denomination[], - meltAmount: AmountJson, - meltFee: AmountJson): Promise<RefreshSession> { - return this.doRpc("createWithdrawSession", + createRefreshSession(exchangeBaseUrl: string, + kappa: number, + meltCoin: Coin, + newCoinDenoms: Denomination[], + meltFee: AmountJson): Promise<RefreshSession> { + return this.doRpc("createRefreshSession", 4, + exchangeBaseUrl, kappa, meltCoin, newCoinDenoms, - meltAmount, meltFee); } }
\ No newline at end of file diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts index d471b577d..3b9d6d228 100644 --- a/lib/wallet/cryptoLib.ts +++ b/lib/wallet/cryptoLib.ts @@ -30,7 +30,7 @@ import create = chrome.alarms.create; import {Offer} from "./wallet"; import {CoinWithDenom} from "./wallet"; import {CoinPaySig} from "./types"; -import {Denomination} from "./types"; +import {Denomination, Amounts} from "./types"; import {Amount} from "./emscriptif"; import {Coin} from "../../background/lib/wallet/types"; import {HashContext} from "./emscriptif"; @@ -151,11 +151,6 @@ namespace RpcFunctions { } - export function hashString(str: string): string { - const b = native.ByteArray.fromString(str); - return b.hash().toCrock(); - } - export function hashRsaPub(rsaPub: string): string { return native.RsaPublicKey.fromCrock(rsaPub) @@ -238,21 +233,36 @@ namespace RpcFunctions { } - export function createWithdrawSession(kappa: number, meltCoin: Coin, - newCoinDenoms: Denomination[], - meltAmount: AmountJson, - meltFee: AmountJson): RefreshSession { + export function createRefreshSession(exchangeBaseUrl: string, + kappa: number, + meltCoin: Coin, + newCoinDenoms: Denomination[], + meltFee: AmountJson): RefreshSession { + + let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); + + for (let ncd of newCoinDenoms) { + valueWithFee = Amounts.add(valueWithFee, + ncd.value, + ncd.fee_withdraw).amount; + } + + // melt fee + valueWithFee = Amounts.add(valueWithFee, meltFee).amount; let sessionHc = new HashContext(); let transferPubs: string[] = []; + let transferPrivs: string[] = []; let preCoinsForGammas: RefreshPreCoin[][] = []; - for (let i = 0; i < newCoinDenoms.length; i++) { - let t = native.EcdsaPrivateKey.create(); - sessionHc.read(t); - transferPubs.push(t.toCrock()); + for (let i = 0; i < kappa; i++) { + let t = native.EcdhePrivateKey.create(); + let pub = t.getPublicKey(); + sessionHc.read(pub); + transferPrivs.push(t.toCrock()); + transferPubs.push(pub.toCrock()); } for (let i = 0; i < newCoinDenoms.length; i++) { @@ -260,18 +270,24 @@ namespace RpcFunctions { sessionHc.read(r.encode()); } - sessionHc.read(native.RsaPublicKey.fromCrock(meltCoin.coinPub).encode()); - sessionHc.read((new native.Amount(meltAmount)).toNbo()); + sessionHc.read(native.EddsaPublicKey.fromCrock(meltCoin.coinPub)); + sessionHc.read((new native.Amount(valueWithFee)).toNbo()); - for (let j = 0; j < kappa; j++) { + for (let i = 0; i < kappa; i++) { let preCoins: RefreshPreCoin[] = []; - for (let i = 0; i < newCoinDenoms.length; i++) { + for (let j = 0; j < newCoinDenoms.length; j++) { + + let transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]); + let oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub); + let transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub); - let coinPriv = native.EddsaPrivateKey.create(); + let fresh = native.setupFreshCoin(transferSecret, j); + + let coinPriv = fresh.priv; let coinPub = coinPriv.getPublicKey(); - let blindingFactor = native.RsaBlindingKeySecret.create(); + let blindingFactor = fresh.blindingKey; let pubHash: native.HashCode = coinPub.hash(); - let denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[i].denom_pub); + let denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denom_pub); let ev = native.rsaBlind(pubHash, blindingFactor, denomPub); @@ -296,11 +312,12 @@ namespace RpcFunctions { let confirmData = new RefreshMeltCoinAffirmationPS({ coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub), - amount_with_fee: (new Amount(meltAmount)).toNbo(), + amount_with_fee: (new Amount(valueWithFee)).toNbo(), session_hash: sessionHash, melt_fee: (new Amount(meltFee)).toNbo() }); + let confirmSig: string = native.eddsaSign(confirmData.toPurpose(), native.EddsaPrivateKey.fromCrock( meltCoin.coinPriv)).toCrock(); @@ -309,9 +326,13 @@ namespace RpcFunctions { meltCoinPub: meltCoin.coinPub, newDenoms: newCoinDenoms.map((d) => d.denom_pub), confirmSig, - valueWithFee: meltAmount, + valueWithFee, transferPubs, preCoinsForGammas, + hash: sessionHash.toCrock(), + norevealIndex: undefined, + exchangeBaseUrl, + transferPrivs, }; return refreshSession; diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts index bad1a4c5e..0be1c1c37 100644 --- a/lib/wallet/emscriptif.ts +++ b/lib/wallet/emscriptif.ts @@ -36,9 +36,18 @@ const GNUNET_SYSERR = -1; let Module = EmscWrapper.Module; -let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply( - null, - args); + +function myCcall(name: string, ret: any, argTypes: any[], args: any[]) { + return Module.ccall(name, ret, argTypes, args); +} + +let getEmsc: EmscWrapper.EmscFunGen = (name: string, ret: any, + argTypes: any[]) => { + return (...args: any[]) => { + return myCcall(name, ret, argTypes, args); + } +}; + var emsc = { free: (ptr: number) => Module._free(ptr), @@ -111,6 +120,15 @@ var emsc = { hash_context_finish: getEmsc('GNUNET_CRYPTO_hash_context_finish', 'void', ['number', 'number']), + ecdh_eddsa: getEmsc( + "GNUNET_CRYPTO_ecdh_eddsa", + 'number', + ["number", "number", "number"]), + + setup_fresh_coin: getEmsc( + "TALER_setup_fresh_coin", + 'void', + ["number", "number", "number"]), }; var emscAlloc = { @@ -121,6 +139,8 @@ var emscAlloc = { 'number', []), ecdsa_key_create: getEmsc('GNUNET_CRYPTO_ecdsa_key_create', 'number', []), + ecdhe_key_create: getEmsc('GNUNET_CRYPTO_ecdhe_key_create', + 'number', []), eddsa_public_key_from_private: getEmsc( 'TALER_WRALL_eddsa_public_key_from_private', 'number', @@ -129,6 +149,10 @@ var emscAlloc = { 'TALER_WRALL_ecdsa_public_key_from_private', 'number', ['number']), + ecdhe_public_key_from_private: getEmsc( + 'TALER_WRALL_ecdhe_public_key_from_private', + 'number', + ['number']), data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc', 'number', ['number', 'number']), @@ -512,7 +536,7 @@ abstract class PackedArenaObject extends MallocArenaObject { this.alloc(); // We need to get the javascript string // to the emscripten heap first. - let buf = ByteArray.fromString(s); + let buf = ByteArray.fromStringWithNull(s); let res = emsc.string_to_data(buf.nativePtr, s.length, this.nativePtr, @@ -618,6 +642,28 @@ export class EcdsaPrivateKey extends PackedArenaObject { mixinStatic(EcdsaPrivateKey, fromCrock); +export class EcdhePrivateKey extends PackedArenaObject { + static create(a?: Arena): EcdhePrivateKey { + let obj = new EcdhePrivateKey(a); + obj.nativePtr = emscAlloc.ecdhe_key_create(); + return obj; + } + + size() { + return 32; + } + + getPublicKey(a?: Arena): EcdhePublicKey { + let obj = new EcdhePublicKey(a); + obj.nativePtr = emscAlloc.ecdhe_public_key_from_private(this.nativePtr); + return obj; + } + + static fromCrock: (s: string) => EcdhePrivateKey; +} +mixinStatic(EcdhePrivateKey, fromCrock); + + function fromCrock(s: string) { let x = new this(); x.alloc(); @@ -664,7 +710,17 @@ export class EcdsaPublicKey extends PackedArenaObject { static fromCrock: (s: string) => EcdsaPublicKey; } -mixinStatic(EddsaPublicKey, fromCrock); +mixinStatic(EcdsaPublicKey, fromCrock); + + +export class EcdhePublicKey extends PackedArenaObject { + size() { + return 32; + } + + static fromCrock: (s: string) => EcdhePublicKey; +} +mixinStatic(EcdhePublicKey, fromCrock); function makeFromCrock(decodeFn: (p: number, s: number) => number) { @@ -747,7 +803,15 @@ export class ByteArray extends PackedArenaObject { this.allocatedSize = desiredSize; } - static fromString(s: string, a?: Arena): ByteArray { + static fromStringWithoutNull(s: string, a?: Arena): ByteArray { + // UTF-8 bytes, including 0-terminator + let terminatedByteLength = countUtf8Bytes(s) + 1; + let hstr = emscAlloc.malloc(terminatedByteLength); + Module.stringToUTF8(s, hstr, terminatedByteLength); + return new ByteArray(terminatedByteLength - 1, hstr, a); + } + + static fromStringWithNull(s: string, a?: Arena): ByteArray { // UTF-8 bytes, including 0-terminator let terminatedByteLength = countUtf8Bytes(s) + 1; let hstr = emscAlloc.malloc(terminatedByteLength); @@ -978,7 +1042,7 @@ export class UInt32 extends PackedArenaObject { } size() { - return 8; + return 4; } } @@ -1185,51 +1249,36 @@ export function rsaUnblind(sig: RsaSignature, type TransferSecretP = HashCode; -export function kdf(outLength: number, - salt: PackedArenaObject, - skm: PackedArenaObject, - ...contextChunks: PackedArenaObject[]): ByteArray { - const args: number[] = []; - let out = new ByteArray(outLength); - args.push(out.nativePtr, outLength); - args.push(salt.nativePtr, salt.size()); - args.push(skm.nativePtr, skm.size()); - for (let chunk of contextChunks) { - args.push(chunk.nativePtr, chunk.size()); - } - // end terminator (it's varargs) - args.push(0); - args.push(0); - - let argTypes = args.map(() => "number"); - - const res = Module.ccall("GNUNET_CRYPTO_kdf", "number", argTypes, args); - if (res != GNUNET_OK) { - throw Error("fatal: kdf failed"); - } - - return out; -} - export interface FreshCoin { priv: EddsaPrivateKey; blindingKey: RsaBlindingKeySecret; } +export function ecdhEddsa(priv: EcdhePrivateKey, + pub: EddsaPublicKey): HashCode { + let h = new HashCode(); + h.alloc(); + let res = emsc.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr); + if (res != GNUNET_OK) { + throw Error("ecdh_eddsa failed"); + } + return h; +} + export function setupFreshCoin(secretSeed: TransferSecretP, coinIndex: number): FreshCoin { let priv = new EddsaPrivateKey(); priv.isWeak = true; let blindingKey = new RsaBlindingKeySecret(); blindingKey.isWeak = true; + let buf = new ByteArray(priv.size() + blindingKey.size()); - let buf = kdf(priv.size() + blindingKey.size(), - UInt32.fromNumber(coinIndex), - ByteArray.fromString("taler-coin-derivation")); + emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr); priv.nativePtr = buf.nativePtr; blindingKey.nativePtr = buf.nativePtr + priv.size(); return {priv, blindingKey}; + }
\ No newline at end of file diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts index 9ff8680ca..f1b1eedce 100644 --- a/lib/wallet/types.ts +++ b/lib/wallet/types.ts @@ -212,6 +212,20 @@ export interface RefreshSession { * The transfer keys, kappa of them. */ transferPubs: string[]; + + transferPrivs: string[]; + + /** + * The no-reveal-index after we've done the melting. + */ + norevealIndex?: number; + + /** + * Hash of the session. + */ + hash: string; + + exchangeBaseUrl: string; } diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 264eef250..43f4227dd 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -1121,27 +1121,89 @@ export class Wallet { let availableDenoms: Denomination[] = exchange.active_denoms; - let newCoinDenoms = getWithdrawDenomList(coin.currentAmount, + let availableAmount = Amounts.sub(coin.currentAmount, + oldDenom.fee_refresh).amount; + + let newCoinDenoms = getWithdrawDenomList(availableAmount, availableDenoms); + newCoinDenoms = [newCoinDenoms[0]]; console.log("refreshing into", newCoinDenoms); let refreshSession: RefreshSession = await ( - this.cryptoApi.createWithdrawSession(3, + this.cryptoApi.createRefreshSession(exchange.baseUrl, + 3, coin, newCoinDenoms, - coin.currentAmount, oldDenom.fee_refresh)); - let reqUrl = URI("reserve/withdraw").absoluteTo(exchange!.baseUrl); - let resp = await this.http.postJson(reqUrl, {}); + let reqUrl = URI("refresh/melt").absoluteTo(exchange!.baseUrl); + let meltCoin = { + coin_pub: coin.coinPub, + denom_pub: coin.denomPub, + denom_sig: coin.denomSig, + confirm_sig: refreshSession.confirmSig, + value_with_fee: refreshSession.valueWithFee, + }; + let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv)); + let req = { + "new_denoms": newCoinDenoms.map((d) => d.denom_pub), + "melt_coin": meltCoin, + "transfer_pubs": refreshSession.transferPubs, + "coin_evs": coinEvs, + }; + console.log("melt request:", req); + let resp = await this.http.postJson(reqUrl, req); + console.log("melt request:", req); console.log("melt response:", resp.responseText); + if (resp.status != 200) { + console.error(resp.responseText); + throw Error("refresh failed"); + } + + let respJson = JSON.parse(resp.responseText); + + if (!respJson) { + throw Error("exchange responded with garbage"); + } + + let norevealIndex = respJson.noreveal_index; + + if (typeof norevealIndex != "number") { + throw Error("invalid response"); + } + + refreshSession.norevealIndex = norevealIndex; + + this.refreshReveal(refreshSession); + // FIXME: implement rest } + async refreshReveal(refreshSession: RefreshSession): Promise<void> { + let norevealIndex = refreshSession.norevealIndex; + if (norevealIndex == undefined) { + throw Error("can't reveal without melting first"); + } + let privs = Array.from(refreshSession.transferPrivs); + privs.splice(norevealIndex, 1); + + let req = { + "session_hash": refreshSession.hash, + "transfer_privs": privs, + }; + + let reqUrl = URI("refresh/reveal").absoluteTo(refreshSession.exchangeBaseUrl); + console.log("reveal request:", req); + let resp = await this.http.postJson(reqUrl, req); + + console.log("session:", refreshSession); + console.log("reveal response:", resp); + } + /** * Retrive the full event history for this wallet. |