working refresh prototype

This commit is contained in:
Florian Dold 2016-10-14 02:13:06 +02:00
parent 0b198e0888
commit ab53892231
6 changed files with 231 additions and 83 deletions

File diff suppressed because one or more lines are too long

View File

@ -114,7 +114,8 @@ export class CryptoApi {
handleWorkerError(ws: WorkerState, e: ErrorEvent) { handleWorkerError(ws: WorkerState, e: ErrorEvent) {
if (ws.currentWorkItem) { if (ws.currentWorkItem) {
console.error(`error in worker during ${ws.currentWorkItem!.operation}`, e); console.error(`error in worker during ${ws.currentWorkItem!.operation}`,
e);
} else { } else {
console.error("error in worker", e); console.error("error in worker", e);
} }
@ -243,16 +244,17 @@ export class CryptoApi {
return this.doRpc("rsaUnblind", 4, sig, bk, pk); return this.doRpc("rsaUnblind", 4, sig, bk, pk);
} }
createWithdrawSession(kappa: number, meltCoin: Coin, createRefreshSession(exchangeBaseUrl: string,
newCoinDenoms: Denomination[], kappa: number,
meltAmount: AmountJson, meltCoin: Coin,
meltFee: AmountJson): Promise<RefreshSession> { newCoinDenoms: Denomination[],
return this.doRpc("createWithdrawSession", meltFee: AmountJson): Promise<RefreshSession> {
return this.doRpc("createRefreshSession",
4, 4,
exchangeBaseUrl,
kappa, kappa,
meltCoin, meltCoin,
newCoinDenoms, newCoinDenoms,
meltAmount,
meltFee); meltFee);
} }
} }

View File

@ -30,7 +30,7 @@ import create = chrome.alarms.create;
import {Offer} from "./wallet"; import {Offer} from "./wallet";
import {CoinWithDenom} from "./wallet"; import {CoinWithDenom} from "./wallet";
import {CoinPaySig} from "./types"; import {CoinPaySig} from "./types";
import {Denomination} from "./types"; import {Denomination, Amounts} from "./types";
import {Amount} from "./emscriptif"; import {Amount} from "./emscriptif";
import {Coin} from "../../background/lib/wallet/types"; import {Coin} from "../../background/lib/wallet/types";
import {HashContext} from "./emscriptif"; 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 { export function hashRsaPub(rsaPub: string): string {
return native.RsaPublicKey.fromCrock(rsaPub) return native.RsaPublicKey.fromCrock(rsaPub)
@ -238,21 +233,36 @@ namespace RpcFunctions {
} }
export function createWithdrawSession(kappa: number, meltCoin: Coin, export function createRefreshSession(exchangeBaseUrl: string,
newCoinDenoms: Denomination[], kappa: number,
meltAmount: AmountJson, meltCoin: Coin,
meltFee: AmountJson): RefreshSession { 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 sessionHc = new HashContext();
let transferPubs: string[] = []; let transferPubs: string[] = [];
let transferPrivs: string[] = [];
let preCoinsForGammas: RefreshPreCoin[][] = []; let preCoinsForGammas: RefreshPreCoin[][] = [];
for (let i = 0; i < newCoinDenoms.length; i++) { for (let i = 0; i < kappa; i++) {
let t = native.EcdsaPrivateKey.create(); let t = native.EcdhePrivateKey.create();
sessionHc.read(t); let pub = t.getPublicKey();
transferPubs.push(t.toCrock()); sessionHc.read(pub);
transferPrivs.push(t.toCrock());
transferPubs.push(pub.toCrock());
} }
for (let i = 0; i < newCoinDenoms.length; i++) { for (let i = 0; i < newCoinDenoms.length; i++) {
@ -260,18 +270,24 @@ namespace RpcFunctions {
sessionHc.read(r.encode()); sessionHc.read(r.encode());
} }
sessionHc.read(native.RsaPublicKey.fromCrock(meltCoin.coinPub).encode()); sessionHc.read(native.EddsaPublicKey.fromCrock(meltCoin.coinPub));
sessionHc.read((new native.Amount(meltAmount)).toNbo()); sessionHc.read((new native.Amount(valueWithFee)).toNbo());
for (let j = 0; j < kappa; j++) { for (let i = 0; i < kappa; i++) {
let preCoins: RefreshPreCoin[] = []; let preCoins: RefreshPreCoin[] = [];
for (let i = 0; i < newCoinDenoms.length; i++) { for (let j = 0; j < newCoinDenoms.length; j++) {
let coinPriv = native.EddsaPrivateKey.create(); let transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]);
let oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub);
let transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
let fresh = native.setupFreshCoin(transferSecret, j);
let coinPriv = fresh.priv;
let coinPub = coinPriv.getPublicKey(); let coinPub = coinPriv.getPublicKey();
let blindingFactor = native.RsaBlindingKeySecret.create(); let blindingFactor = fresh.blindingKey;
let pubHash: native.HashCode = coinPub.hash(); 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, let ev = native.rsaBlind(pubHash,
blindingFactor, blindingFactor,
denomPub); denomPub);
@ -296,11 +312,12 @@ namespace RpcFunctions {
let confirmData = new RefreshMeltCoinAffirmationPS({ let confirmData = new RefreshMeltCoinAffirmationPS({
coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub), coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
amount_with_fee: (new Amount(meltAmount)).toNbo(), amount_with_fee: (new Amount(valueWithFee)).toNbo(),
session_hash: sessionHash, session_hash: sessionHash,
melt_fee: (new Amount(meltFee)).toNbo() melt_fee: (new Amount(meltFee)).toNbo()
}); });
let confirmSig: string = native.eddsaSign(confirmData.toPurpose(), let confirmSig: string = native.eddsaSign(confirmData.toPurpose(),
native.EddsaPrivateKey.fromCrock( native.EddsaPrivateKey.fromCrock(
meltCoin.coinPriv)).toCrock(); meltCoin.coinPriv)).toCrock();
@ -309,9 +326,13 @@ namespace RpcFunctions {
meltCoinPub: meltCoin.coinPub, meltCoinPub: meltCoin.coinPub,
newDenoms: newCoinDenoms.map((d) => d.denom_pub), newDenoms: newCoinDenoms.map((d) => d.denom_pub),
confirmSig, confirmSig,
valueWithFee: meltAmount, valueWithFee,
transferPubs, transferPubs,
preCoinsForGammas, preCoinsForGammas,
hash: sessionHash.toCrock(),
norevealIndex: undefined,
exchangeBaseUrl,
transferPrivs,
}; };
return refreshSession; return refreshSession;

View File

@ -36,9 +36,18 @@ const GNUNET_SYSERR = -1;
let Module = EmscWrapper.Module; let Module = EmscWrapper.Module;
let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply(
null, function myCcall(name: string, ret: any, argTypes: any[], args: any[]) {
args); 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 = { var emsc = {
free: (ptr: number) => Module._free(ptr), free: (ptr: number) => Module._free(ptr),
@ -111,6 +120,15 @@ var emsc = {
hash_context_finish: getEmsc('GNUNET_CRYPTO_hash_context_finish', hash_context_finish: getEmsc('GNUNET_CRYPTO_hash_context_finish',
'void', 'void',
['number', 'number']), ['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 = { var emscAlloc = {
@ -121,6 +139,8 @@ var emscAlloc = {
'number', []), 'number', []),
ecdsa_key_create: getEmsc('GNUNET_CRYPTO_ecdsa_key_create', ecdsa_key_create: getEmsc('GNUNET_CRYPTO_ecdsa_key_create',
'number', []), 'number', []),
ecdhe_key_create: getEmsc('GNUNET_CRYPTO_ecdhe_key_create',
'number', []),
eddsa_public_key_from_private: getEmsc( eddsa_public_key_from_private: getEmsc(
'TALER_WRALL_eddsa_public_key_from_private', 'TALER_WRALL_eddsa_public_key_from_private',
'number', 'number',
@ -129,6 +149,10 @@ var emscAlloc = {
'TALER_WRALL_ecdsa_public_key_from_private', 'TALER_WRALL_ecdsa_public_key_from_private',
'number', 'number',
['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', data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc',
'number', 'number',
['number', 'number']), ['number', 'number']),
@ -512,7 +536,7 @@ abstract class PackedArenaObject extends MallocArenaObject {
this.alloc(); this.alloc();
// We need to get the javascript string // We need to get the javascript string
// to the emscripten heap first. // to the emscripten heap first.
let buf = ByteArray.fromString(s); let buf = ByteArray.fromStringWithNull(s);
let res = emsc.string_to_data(buf.nativePtr, let res = emsc.string_to_data(buf.nativePtr,
s.length, s.length,
this.nativePtr, this.nativePtr,
@ -618,6 +642,28 @@ export class EcdsaPrivateKey extends PackedArenaObject {
mixinStatic(EcdsaPrivateKey, fromCrock); 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) { function fromCrock(s: string) {
let x = new this(); let x = new this();
x.alloc(); x.alloc();
@ -664,7 +710,17 @@ export class EcdsaPublicKey extends PackedArenaObject {
static fromCrock: (s: string) => EcdsaPublicKey; 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) { function makeFromCrock(decodeFn: (p: number, s: number) => number) {
@ -747,7 +803,15 @@ export class ByteArray extends PackedArenaObject {
this.allocatedSize = desiredSize; 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 // UTF-8 bytes, including 0-terminator
let terminatedByteLength = countUtf8Bytes(s) + 1; let terminatedByteLength = countUtf8Bytes(s) + 1;
let hstr = emscAlloc.malloc(terminatedByteLength); let hstr = emscAlloc.malloc(terminatedByteLength);
@ -978,7 +1042,7 @@ export class UInt32 extends PackedArenaObject {
} }
size() { size() {
return 8; return 4;
} }
} }
@ -1185,51 +1249,36 @@ export function rsaUnblind(sig: RsaSignature,
type TransferSecretP = HashCode; 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 { export interface FreshCoin {
priv: EddsaPrivateKey; priv: EddsaPrivateKey;
blindingKey: RsaBlindingKeySecret; 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, export function setupFreshCoin(secretSeed: TransferSecretP,
coinIndex: number): FreshCoin { coinIndex: number): FreshCoin {
let priv = new EddsaPrivateKey(); let priv = new EddsaPrivateKey();
priv.isWeak = true; priv.isWeak = true;
let blindingKey = new RsaBlindingKeySecret(); let blindingKey = new RsaBlindingKeySecret();
blindingKey.isWeak = true; blindingKey.isWeak = true;
let buf = new ByteArray(priv.size() + blindingKey.size());
let buf = kdf(priv.size() + blindingKey.size(), emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr);
UInt32.fromNumber(coinIndex),
ByteArray.fromString("taler-coin-derivation"));
priv.nativePtr = buf.nativePtr; priv.nativePtr = buf.nativePtr;
blindingKey.nativePtr = buf.nativePtr + priv.size(); blindingKey.nativePtr = buf.nativePtr + priv.size();
return {priv, blindingKey}; return {priv, blindingKey};
} }

View File

@ -212,6 +212,20 @@ export interface RefreshSession {
* The transfer keys, kappa of them. * The transfer keys, kappa of them.
*/ */
transferPubs: string[]; transferPubs: string[];
transferPrivs: string[];
/**
* The no-reveal-index after we've done the melting.
*/
norevealIndex?: number;
/**
* Hash of the session.
*/
hash: string;
exchangeBaseUrl: string;
} }

View File

@ -1121,27 +1121,89 @@ export class Wallet {
let availableDenoms: Denomination[] = exchange.active_denoms; 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); availableDenoms);
newCoinDenoms = [newCoinDenoms[0]];
console.log("refreshing into", newCoinDenoms); console.log("refreshing into", newCoinDenoms);
let refreshSession: RefreshSession = await ( let refreshSession: RefreshSession = await (
this.cryptoApi.createWithdrawSession(3, this.cryptoApi.createRefreshSession(exchange.baseUrl,
3,
coin, coin,
newCoinDenoms, newCoinDenoms,
coin.currentAmount,
oldDenom.fee_refresh)); oldDenom.fee_refresh));
let reqUrl = URI("reserve/withdraw").absoluteTo(exchange!.baseUrl); let reqUrl = URI("refresh/melt").absoluteTo(exchange!.baseUrl);
let resp = await this.http.postJson(reqUrl, {}); 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); 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 // 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. * Retrive the full event history for this wallet.