aboutsummaryrefslogtreecommitdiff
path: root/lib/wallet
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-10-14 02:13:06 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-10-14 02:13:06 +0200
commitab538922312a37da5ee302e34fb72af7a0f8bae7 (patch)
treeb632cbc674d81f9057820098a7ad2996d1203b13 /lib/wallet
parent0b198e08888830890622e983445c75f947186b4c (diff)
working refresh prototype
Diffstat (limited to 'lib/wallet')
-rw-r--r--lib/wallet/cryptoApi.ts16
-rw-r--r--lib/wallet/cryptoLib.ts67
-rw-r--r--lib/wallet/emscriptif.ts121
-rw-r--r--lib/wallet/types.ts14
-rw-r--r--lib/wallet/wallet.ts72
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.