wallet-core: deterministic p2p contract encryption

This commit is contained in:
Florian Dold 2023-06-06 15:00:10 +02:00
parent 9d35a7dc9b
commit 474a171f5e
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
6 changed files with 98 additions and 46 deletions

View File

@ -1465,6 +1465,7 @@ export function encryptContractForMerge(
contractPriv: ContractPrivateKey,
mergePriv: MergePrivateKey,
contractTerms: any,
nonce: EncryptionNonce,
): Promise<OpaqueData> {
const contractTermsCanon = canonicalJson(contractTerms) + "\0";
const contractTermsBytes = stringToBytes(contractTermsCanon);
@ -1476,13 +1477,14 @@ export function encryptContractForMerge(
contractTermsCompressed,
]);
const key = keyExchangeEcdhEddsa(contractPriv, pursePub);
return encryptWithDerivedKey(getRandomBytesF(24), key, data, mergeSalt);
return encryptWithDerivedKey(nonce, key, data, mergeSalt);
}
export function encryptContractForDeposit(
pursePub: PursePublicKey,
contractPriv: ContractPrivateKey,
contractTerms: any,
nonce: EncryptionNonce,
): Promise<OpaqueData> {
const contractTermsCanon = canonicalJson(contractTerms) + "\0";
const contractTermsBytes = stringToBytes(contractTermsCanon);
@ -1493,7 +1495,7 @@ export function encryptContractForDeposit(
contractTermsCompressed,
]);
const key = keyExchangeEcdhEddsa(contractPriv, pursePub);
return encryptWithDerivedKey(getRandomBytesF(24), key, data, depositSalt);
return encryptWithDerivedKey(nonce, key, data, depositSalt);
}
export interface DecryptForMergeResult {

View File

@ -1495,6 +1495,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
decodeCrock(req.contractPriv),
decodeCrock(req.mergePriv),
req.contractTerms,
decodeCrock(req.nonce),
);
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
.put(hash(enc))
@ -1531,6 +1532,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
decodeCrock(req.pursePub),
decodeCrock(req.contractPriv),
req.contractTerms,
decodeCrock(req.nonce),
);
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
.put(hash(enc))

View File

@ -181,6 +181,7 @@ export interface EncryptContractRequest {
pursePub: string;
pursePriv: string;
mergePriv: string;
nonce: string;
}
export interface EncryptContractResponse {
@ -195,6 +196,8 @@ export interface EncryptContractForDepositRequest {
pursePub: string;
pursePriv: string;
nonce: string;
}
export interface EncryptContractForDepositResponse {

View File

@ -1839,6 +1839,14 @@ export interface PeerPushPaymentInitiationRecord {
contractPriv: string;
contractPub: string;
/**
* 24 byte nonce.
*/
contractEncNonce: string;
/**
* FIXME: Put those in a different object store!
*/
contractTerms: PeerContractTerms;
purseExpiration: TalerProtocolTimestamp;
@ -1911,6 +1919,11 @@ export interface PeerPullPaymentInitiationRecord {
contractPub: string;
contractPriv: string;
contractEncNonce: string;
/**
* FIXME: Put in separate object store!
*/
contractTerms: PeerContractTerms;
mergeTimestamp: TalerPreciseTimestamp;

View File

@ -405,6 +405,7 @@ export async function processPeerPullCredit(
contractTerms: pullIni.contractTerms,
pursePriv: pullIni.pursePriv,
pursePub: pullIni.pursePub,
nonce: pullIni.contractEncNonce,
});
const purseExpiration = pullIni.contractTerms.purse_expiration;
@ -690,6 +691,8 @@ export async function initiatePeerPullPayment(
const mergeReserveRowId = mergeReserveInfo.rowId;
checkDbInvariant(!!mergeReserveRowId);
const contractEncNonce = encodeCrock(getRandomBytes(24));
const wi = await getExchangeWithdrawalInfo(
ws,
exchangeBaseUrl,
@ -711,6 +714,7 @@ export async function initiatePeerPullPayment(
status: PeerPullPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms,
mergeTimestamp,
contractEncNonce,
mergeReserveRowId: mergeReserveRowId,
contractPriv: contractKeyPair.priv,
contractPub: contractKeyPair.pub,

View File

@ -34,6 +34,10 @@ import {
TransactionMinorState,
TransactionState,
TransactionType,
decodeCrock,
encodeCrock,
getRandomBytes,
hash,
j2s,
stringifyTalerUri,
} from "@gnu-taler/taler-util";
@ -57,11 +61,7 @@ import {
OperationAttemptResultType,
constructTaskIdentifier,
} from "../util/retries.js";
import {
runLongpollAsync,
spendCoins,
runOperationWithErrorReporting,
} from "./common.js";
import { runLongpollAsync, spendCoins } from "./common.js";
import {
constructTransactionIdentifier,
notifyTransition,
@ -69,6 +69,7 @@ import {
} from "./transactions.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkLogicInvariant } from "../util/invariants.js";
import { EncryptContractRequest } from "../crypto/cryptoTypes.js";
const logger = new Logger("pay-peer-push-debit.ts");
@ -100,6 +101,7 @@ async function processPeerPushDebitCreateReserve(
ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> {
logger.info("processing peer-push-debit pending(create-reserve)");
const pursePub = peerPushInitiation.pursePub;
const purseExpiration = peerPushInitiation.purseExpiration;
const hContractTerms = peerPushInitiation.contractTermsHash;
@ -124,32 +126,49 @@ async function processPeerPushDebitCreateReserve(
coins,
});
const econtractResp = await ws.cryptoApi.encryptContractForMerge({
const encryptContractRequest: EncryptContractRequest = {
contractTerms: peerPushInitiation.contractTerms,
mergePriv: peerPushInitiation.mergePriv,
pursePriv: peerPushInitiation.pursePriv,
pursePub: peerPushInitiation.pursePub,
contractPriv: peerPushInitiation.contractPriv,
contractPub: peerPushInitiation.contractPub,
});
nonce: peerPushInitiation.contractEncNonce,
};
logger.info(`encrypt contract request: ${j2s(encryptContractRequest)}`);
const econtractResp = await ws.cryptoApi.encryptContractForMerge(
encryptContractRequest,
);
const econtractHash = encodeCrock(
hash(decodeCrock(econtractResp.econtract.econtract)),
);
logger.info(`econtract hash: ${econtractHash}`);
const createPurseUrl = new URL(
`purses/${peerPushInitiation.pursePub}/create`,
peerPushInitiation.exchangeBaseUrl,
);
const reqBody = {
amount: peerPushInitiation.amount,
merge_pub: peerPushInitiation.mergePub,
purse_sig: purseSigResp.sig,
h_contract_terms: hContractTerms,
purse_expiration: purseExpiration,
deposits: depositSigsResp.deposits,
min_age: 0,
econtract: econtractResp.econtract,
};
logger.info(`request body: ${j2s(reqBody)}`);
const httpResp = await ws.http.fetch(createPurseUrl.href, {
method: "POST",
body: {
amount: peerPushInitiation.amount,
merge_pub: peerPushInitiation.mergePub,
purse_sig: purseSigResp.sig,
h_contract_terms: hContractTerms,
purse_expiration: purseExpiration,
deposits: depositSigsResp.deposits,
min_age: 0,
econtract: econtractResp.econtract,
},
body: reqBody,
});
const resp = await httpResp.json();
@ -157,24 +176,16 @@ async function processPeerPushDebitCreateReserve(
logger.info(`resp: ${j2s(resp)}`);
if (httpResp.status !== HttpStatusCode.Ok) {
// FIXME: do proper error reporting
throw Error("got error response from exchange");
}
await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const ppi = await tx.peerPushPaymentInitiations.get(pursePub);
if (!ppi) {
return;
}
ppi.status = PeerPushPaymentInitiationStatus.Done;
await tx.peerPushPaymentInitiations.put(ppi);
});
await transitionPeerPushDebitTransaction(ws, pursePub, {
stFrom: PeerPushPaymentInitiationStatus.PendingCreatePurse,
stTo: PeerPushPaymentInitiationStatus.PendingReady,
});
return {
type: OperationAttemptResultType.Finished,
result: undefined,
};
return OperationAttemptResult.finishedEmpty();
}
async function processPeerPushDebitAbortingDeletePurse(
@ -278,6 +289,7 @@ async function transitionPeerPushDebitTransaction(
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
ppiRec.status = transitionSpec.stTo;
const newTxState = computePeerPushDebitTransactionState(ppiRec);
// FIXME: We don't transition here?!
return {
oldTxState,
newTxState,
@ -341,6 +353,7 @@ async function processPeerPushDebitReady(
ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> {
logger.info("processing peer-push-debit pending(ready)");
const pursePub = peerPushInitiation.pursePub;
const retryTag = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
@ -434,6 +447,12 @@ export async function processPeerPushDebit(
return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation);
case PeerPushPaymentInitiationStatus.AbortingRefresh:
return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation);
default: {
const txState = computePeerPushDebitTransactionState(peerPushInitiation);
logger.warn(
`not processing peer-push-debit transaction in state ${j2s(txState)}`,
);
}
}
return {
@ -482,7 +501,16 @@ export async function initiatePeerPushDebit(
coinSelRes.result.coins,
);
await ws.db
const pursePub = pursePair.pub;
const transactionId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
const contractEncNonce = encodeCrock(getRandomBytes(24));
const transitionInfo = await ws.db
.mktx((x) => [
x.exchanges,
x.contractTerms,
@ -509,7 +537,7 @@ export async function initiatePeerPushDebit(
refreshReason: RefreshReason.PayPeerPush,
});
await tx.peerPushPaymentInitiations.add({
const ppi: PeerPushPaymentInitiationRecord = {
amount: Amounts.stringify(instructedAmount),
contractPriv: contractKeyPair.priv,
contractPub: contractKeyPair.pub,
@ -523,27 +551,28 @@ export async function initiatePeerPushDebit(
timestampCreated: TalerPreciseTimestamp.now(),
status: PeerPushPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms,
contractEncNonce,
coinSel: {
coinPubs: sel.coins.map((x) => x.coinPub),
contributions: sel.coins.map((x) => x.contribution),
},
totalCost: Amounts.stringify(totalAmount),
});
};
await tx.peerPushPaymentInitiations.add(ppi);
await tx.contractTerms.put({
h: hContractTerms,
contractTermsRaw: contractTerms,
});
const newTxState = computePeerPushDebitTransactionState(ppi);
return {
oldTxState: { major: TransactionMajorState.None },
newTxState,
};
});
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub: pursePair.pub,
});
await runOperationWithErrorReporting(ws, taskId, async () => {
return await processPeerPushDebit(ws, pursePair.pub);
});
notifyTransition(ws, transactionId, transitionInfo);
return {
contractPriv: contractKeyPair.priv,
@ -903,4 +932,3 @@ export function computePeerPushDebitTransactionState(
};
}
}