wallet-core: fix tipping with age restricted denoms
This commit is contained in:
parent
ffe6a95214
commit
f63765b9f7
@ -724,6 +724,8 @@ export interface FreshCoin {
|
||||
coinPub: Uint8Array;
|
||||
coinPriv: Uint8Array;
|
||||
bks: Uint8Array;
|
||||
maxAge: number;
|
||||
ageCommitmentProof: AgeCommitmentProof | undefined;
|
||||
}
|
||||
|
||||
export function bufferForUint32(n: number): Uint8Array {
|
||||
@ -742,10 +744,11 @@ export function bufferForUint8(n: number): Uint8Array {
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function setupTipPlanchet(
|
||||
export async function setupTipPlanchet(
|
||||
secretSeed: Uint8Array,
|
||||
denomPub: DenominationPubKey,
|
||||
coinNumber: number,
|
||||
): FreshCoin {
|
||||
): Promise<FreshCoin> {
|
||||
const info = stringToBytes("taler-tip-coin-derivation");
|
||||
const saltArrBuf = new ArrayBuffer(4);
|
||||
const salt = new Uint8Array(saltArrBuf);
|
||||
@ -754,10 +757,20 @@ export function setupTipPlanchet(
|
||||
const out = kdf(64, secretSeed, salt, info);
|
||||
const coinPriv = out.slice(0, 32);
|
||||
const bks = out.slice(32, 64);
|
||||
let maybeAcp: AgeCommitmentProof | undefined;
|
||||
if (denomPub.age_mask != 0) {
|
||||
maybeAcp = await AgeRestriction.restrictionCommitSeeded(
|
||||
denomPub.age_mask,
|
||||
AgeRestriction.AGE_UNRESTRICTED,
|
||||
secretSeed,
|
||||
);
|
||||
}
|
||||
return {
|
||||
bks,
|
||||
coinPriv,
|
||||
coinPub: eddsaGetPublic(coinPriv),
|
||||
maxAge: AgeRestriction.AGE_UNRESTRICTED,
|
||||
ageCommitmentProof: maybeAcp,
|
||||
};
|
||||
}
|
||||
/**
|
||||
@ -1062,6 +1075,44 @@ export namespace AgeRestriction {
|
||||
};
|
||||
}
|
||||
|
||||
export async function restrictionCommitSeeded(
|
||||
ageMask: number,
|
||||
age: number,
|
||||
seed: Uint8Array,
|
||||
): Promise<AgeCommitmentProof> {
|
||||
invariant((ageMask & 1) === 1);
|
||||
const numPubs = countAgeGroups(ageMask) - 1;
|
||||
const numPrivs = getAgeGroupIndex(ageMask, age);
|
||||
|
||||
const pubs: Edx25519PublicKey[] = [];
|
||||
const privs: Edx25519PrivateKey[] = [];
|
||||
|
||||
for (let i = 0; i < numPubs; i++) {
|
||||
const privSeed = await kdfKw({
|
||||
outputLength: 32,
|
||||
ikm: seed,
|
||||
info: stringToBytes("age-restriction-commit"),
|
||||
salt: bufferForUint32(i),
|
||||
});
|
||||
const priv = await Edx25519.keyCreateFromSeed(privSeed);
|
||||
const pub = await Edx25519.getPublic(priv);
|
||||
pubs.push(pub);
|
||||
if (i < numPrivs) {
|
||||
privs.push(priv);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
commitment: {
|
||||
mask: ageMask,
|
||||
publicKeys: pubs.map((x) => encodeCrock(x)),
|
||||
},
|
||||
proof: {
|
||||
privateKeys: privs.map((x) => encodeCrock(x)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that c1 = c2*salt
|
||||
*/
|
||||
|
@ -1991,8 +1991,7 @@ export function getRandomIban(salt: string | null = null): string {
|
||||
return `DE${check_digits}${bban}`;
|
||||
}
|
||||
|
||||
// Only used in one tipping test.
|
||||
export function getWireMethod(): string {
|
||||
export function getWireMethodForTest(): string {
|
||||
if (useLibeufinBank) return "iban";
|
||||
return "x-taler-bank";
|
||||
}
|
||||
|
@ -17,8 +17,14 @@
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { BankApi, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||
import { defaultCoinConfig } from "../harness/denomStructures.js";
|
||||
import { GlobalTestState, WalletCli } from "../harness/harness.js";
|
||||
import {
|
||||
getWireMethodForTest,
|
||||
GlobalTestState,
|
||||
MerchantPrivateApi,
|
||||
WalletCli,
|
||||
} from "../harness/harness.js";
|
||||
import {
|
||||
createSimpleTestkudosEnvironment,
|
||||
withdrawViaBank,
|
||||
@ -31,14 +37,19 @@ import {
|
||||
export async function runAgeRestrictionsMerchantTest(t: GlobalTestState) {
|
||||
// Set up test environment
|
||||
|
||||
const { wallet: walletOne, bank, exchange, merchant } =
|
||||
await createSimpleTestkudosEnvironment(
|
||||
t,
|
||||
defaultCoinConfig.map((x) => x("TESTKUDOS")),
|
||||
{
|
||||
ageMaskSpec: "8:10:12:14:16:18:21",
|
||||
},
|
||||
);
|
||||
const {
|
||||
wallet: walletOne,
|
||||
bank,
|
||||
exchange,
|
||||
merchant,
|
||||
exchangeBankAccount,
|
||||
} = await createSimpleTestkudosEnvironment(
|
||||
t,
|
||||
defaultCoinConfig.map((x) => x("TESTKUDOS")),
|
||||
{
|
||||
ageMaskSpec: "8:10:12:14:16:18:21",
|
||||
},
|
||||
);
|
||||
|
||||
const walletTwo = new WalletCli(t, "walletTwo");
|
||||
const walletThree = new WalletCli(t, "walletThree");
|
||||
@ -129,6 +140,62 @@ export async function runAgeRestrictionsMerchantTest(t: GlobalTestState) {
|
||||
await wallet.runUntilDone();
|
||||
}
|
||||
|
||||
// Pay with coin from tipping
|
||||
{
|
||||
const mbu = await BankApi.createRandomBankUser(bank);
|
||||
const tipReserveResp = await MerchantPrivateApi.createTippingReserve(
|
||||
merchant,
|
||||
"default",
|
||||
{
|
||||
exchange_url: exchange.baseUrl,
|
||||
initial_balance: "TESTKUDOS:10",
|
||||
wire_method: getWireMethodForTest(),
|
||||
},
|
||||
);
|
||||
|
||||
t.assertDeepEqual(
|
||||
tipReserveResp.payto_uri,
|
||||
exchangeBankAccount.accountPaytoUri,
|
||||
);
|
||||
|
||||
await BankApi.adminAddIncoming(bank, {
|
||||
amount: "TESTKUDOS:10",
|
||||
debitAccountPayto: mbu.accountPaytoUri,
|
||||
exchangeBankAccount,
|
||||
reservePub: tipReserveResp.reserve_pub,
|
||||
});
|
||||
|
||||
await exchange.runWirewatchOnce();
|
||||
|
||||
const tip = await MerchantPrivateApi.giveTip(merchant, "default", {
|
||||
amount: "TESTKUDOS:5",
|
||||
justification: "why not?",
|
||||
next_url: "https://example.com/after-tip",
|
||||
});
|
||||
|
||||
const walletTipping = new WalletCli(t, "age-tipping");
|
||||
|
||||
const ptr = await walletTipping.client.call(WalletApiOperation.PrepareTip, {
|
||||
talerTipUri: tip.taler_tip_uri,
|
||||
});
|
||||
|
||||
await walletTipping.client.call(WalletApiOperation.AcceptTip, {
|
||||
walletTipId: ptr.walletTipId,
|
||||
});
|
||||
|
||||
await walletTipping.runUntilDone();
|
||||
|
||||
const order = {
|
||||
summary: "Buy me!",
|
||||
amount: "TESTKUDOS:4",
|
||||
fulfillment_url: "taler://fulfillment-success/thx",
|
||||
minimum_age: 9,
|
||||
};
|
||||
|
||||
await makeTestPayment(t, { wallet: walletTipping, merchant, order });
|
||||
await walletTipping.runUntilDone();
|
||||
}
|
||||
}
|
||||
|
||||
runAgeRestrictionsMerchantTest.suites = ["wallet"];
|
||||
runAgeRestrictionsMerchantTest.timeoutMs = 120 * 1000;
|
||||
|
@ -21,7 +21,7 @@ import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core";
|
||||
import {
|
||||
GlobalTestState,
|
||||
MerchantPrivateApi,
|
||||
getWireMethod,
|
||||
getWireMethodForTest,
|
||||
} from "../harness/harness.js";
|
||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||
|
||||
@ -42,7 +42,7 @@ export async function runTippingTest(t: GlobalTestState) {
|
||||
{
|
||||
exchange_url: exchange.baseUrl,
|
||||
initial_balance: "TESTKUDOS:10",
|
||||
wire_method: getWireMethod(),
|
||||
wire_method: getWireMethodForTest(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -743,9 +743,16 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
||||
if (req.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||
throw Error(`unsupported cipher (${req.denomPub.cipher})`);
|
||||
}
|
||||
const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex);
|
||||
const fc = await setupTipPlanchet(
|
||||
decodeCrock(req.secretSeed),
|
||||
req.denomPub,
|
||||
req.planchetIndex,
|
||||
);
|
||||
const maybeAch = fc.ageCommitmentProof
|
||||
? AgeRestriction.hashCommitment(fc.ageCommitmentProof.commitment)
|
||||
: undefined;
|
||||
const denomPub = decodeCrock(req.denomPub.rsa_public_key);
|
||||
const coinPubHash = hash(fc.coinPub);
|
||||
const coinPubHash = hashCoinPub(encodeCrock(fc.coinPub), maybeAch);
|
||||
const blindResp = await tci.rsaBlind(tci, {
|
||||
bks: encodeCrock(fc.bks),
|
||||
hm: encodeCrock(coinPubHash),
|
||||
@ -763,6 +770,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
||||
),
|
||||
coinPriv: encodeCrock(fc.coinPriv),
|
||||
coinPub: encodeCrock(fc.coinPub),
|
||||
ageCommitmentProof: fc.ageCommitmentProof,
|
||||
};
|
||||
return tipPlanchet;
|
||||
},
|
||||
|
@ -122,6 +122,7 @@ export interface DerivedTipPlanchet {
|
||||
coinEvHash: string;
|
||||
coinPriv: string;
|
||||
coinPub: string;
|
||||
ageCommitmentProof: AgeCommitmentProof | undefined;
|
||||
}
|
||||
|
||||
export interface SignTrackTransactionRequest {
|
||||
|
@ -319,6 +319,7 @@ export async function processTip(
|
||||
status: CoinStatus.Fresh,
|
||||
coinEvHash: planchet.coinEvHash,
|
||||
maxAge: AgeRestriction.AGE_UNRESTRICTED,
|
||||
ageCommitmentProof: planchet.ageCommitmentProof,
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user