wallet-core: deterministic p2p contract encryption
This commit is contained in:
parent
9d35a7dc9b
commit
474a171f5e
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user