Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

/* eslint-disable security/detect-object-injection */

import { TokenRequest } from '../src/arbitrary_batched_token.js';
import { type Token, type TokenChallenge, arbitraryBatched, publicVerif } from '../src/index.js';
import { TokenRequest } from '../src/generic_batched_token.js';
import { type Token, type TokenChallenge, genericBatched, publicVerif } from '../src/index.js';
type BlindRSAMode = publicVerif.BlindRSAMode;
const { Client, Issuer } = arbitraryBatched;
const { Client, Issuer } = genericBatched;

async function setupPublicVerif(mode: BlindRSAMode) {
// [ Issuer ] creates a key pair.
Expand Down Expand Up @@ -82,8 +82,8 @@ async function rsaVariant(): Promise<boolean> {
if (res.tokenResponse === null) {
continue;
}
const r = publicVerif.TokenResponse.deserialize(res.tokenResponse);
tokens[i] = await clients[i].finalize(r);
const r = res.tokenResponse;
tokens[i] = await clients[i].finalize(r as publicVerif.TokenResponse);

i += 1;
}
Expand All @@ -95,11 +95,11 @@ async function rsaVariant(): Promise<boolean> {
isValid &&= token !== undefined && (await origins[i].verify(token, issuers[i].publicKey));
}

console.log('Arbitrary batched tokens');
console.log('Generic batched tokens');
console.log(` Valid token: ${isValid}`);
return isValid;
}

export function arbitraryBatchedTokens(): Promise<boolean> {
export function genericBatchedTokens(): Promise<boolean> {
return rsaVariant();
}
4 changes: 2 additions & 2 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { webcrypto } from 'node:crypto';

import { arbitraryBatchedTokens } from './arbitrary_batched.example.js';
import { genericBatchedTokens } from './generic_batched.example.js';
import { publicVerifiableTokensPSS, publicVerifiableTokensPSSZero } from './pub_verif.example.js';
import {
publicVerifiableWithMetadataTokensPSS,
Expand All @@ -23,7 +23,7 @@ async function isOk(fn: () => Promise<boolean>) {
}

async function examples() {
await isOk(arbitraryBatchedTokens);
await isOk(genericBatchedTokens);
await isOk(privateVerifiableTokens);
await isOk(publicVerifiableTokensPSS);
await isOk(publicVerifiableTokensPSSZero);
Expand Down
45 changes: 9 additions & 36 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"devDependencies": {
"@eslint/js": "9.9.1",
"@types/benchmark": "2.1.5",
"@types/node": "^25.2.3",
"@vitest/coverage-v8": "3.0.7",
"benchmark": "2.1.4",
"eslint": "8.57.1",
Expand Down
96 changes: 62 additions & 34 deletions src/arbitrary_batched_token.ts → src/generic_batched_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@

import * as varint from 'quicvarint';

import type { privateVerif, publicVerif } from './index.js';
import {
type Token,
TOKEN_TYPES,
tokenEntryToSerializedLength,
tokenRequestToTokenTypeEntry,
} from './index.js';
import { Issuer as Type1Issuer, TokenRequest as Type1TokenRequest } from './priv_verif_token.js';
import { Issuer as Type2Issuer, TokenRequest as Type2TokenRequest } from './pub_verif_token.js';
import {
TokenResponse,
Issuer as Type2Issuer,
TokenRequest as Type2TokenRequest,
} from './pub_verif_token.js';
import { joinAll } from './util.js';

const TokenStatus = {
ABSENT: 0x00,
PRESENT: 0x01,
} as const;

type TokenStatus = (typeof TokenStatus)[keyof typeof TokenStatus];

export class TokenRequest {
// struct {
// uint16_t token_type;
Expand Down Expand Up @@ -121,35 +133,52 @@ export class BatchedTokenRequest {

export class OptionalTokenResponse {
// struct {
// TokenResponse token_response<0..2^16-1>; /* Defined by token_type */
// optional<GenericTokenResponse> generic_token_response; /* Defined by token_type */
// } OptionalTokenResponse;
constructor(public readonly tokenResponse: null | Uint8Array) {}

static deserialize(bytes: Uint8Array): OptionalTokenResponse {
constructor(
public readonly tokenResponse:
| null
| publicVerif.TokenResponse
| privateVerif.TokenResponse,
) {}

static deserialize(bytes: Uint8Array, type: 1 | 2): OptionalTokenResponse {
if (bytes.length === 0) {
return new OptionalTokenResponse(null);
throw new Error('OptionalTokenResponse MUST be of length strictly greater than 0');
}
switch (bytes[0]) {
case TokenStatus.ABSENT:
return new OptionalTokenResponse(null);
case TokenStatus.PRESENT:
switch (type) {
case TOKEN_TYPES.VOPRF.value:
return new OptionalTokenResponse(TokenResponse.deserialize(bytes.slice(1)));
case TOKEN_TYPES.BLIND_RSA.value:
return new OptionalTokenResponse(TokenResponse.deserialize(bytes.slice(1)));
default:
throw new Error('unsupported token type');
}
default:
throw new Error('OptionalTokenResponse MUST start with either 0x00 or 0x01');
}
return new OptionalTokenResponse(bytes);
}

serialize(): Uint8Array {
if (this.tokenResponse === null) {
return new Uint8Array();
return new Uint8Array([TokenStatus.ABSENT]);
}
return this.tokenResponse;
const serialized = this.tokenResponse.serialize();
return new Uint8Array([TokenStatus.PRESENT, ...serialized]);
}
}

// struct {
// OptionalTokenResponse token_responses<0..2^16-1>;
// } BatchTokenResponse
export class BatchedTokenResponse {
export class GenericBatchTokenResponse {
// struct {
// TokenRequest token_requests<V>;
// } BatchTokenRequest
// OptionalTokenResponse optional_token_responses<V>;
// } GenericBatchTokenResponse
constructor(public readonly tokenResponses: OptionalTokenResponse[]) {}

static deserialize(bytes: Uint8Array): BatchedTokenResponse {
static deserialize(bytes: Uint8Array, types: (1 | 2)[]): GenericBatchTokenResponse {
let offset = 0;
const input = new DataView(bytes.buffer);

Expand All @@ -162,16 +191,19 @@ export class BatchedTokenResponse {

const batchedTokenResponses: OptionalTokenResponse[] = [];

let i = 0;
while (offset < bytes.length) {
const len = input.getUint16(offset);
offset += 2;
const b = new Uint8Array(input.buffer.slice(offset, offset + len));
offset += len;

batchedTokenResponses.push(OptionalTokenResponse.deserialize(b));
const type = types[i++];
const otr = OptionalTokenResponse.deserialize(bytes.slice(offset), type);
if (otr.tokenResponse === null) {
offset += 1;
} else {
offset += otr.tokenResponse.length() + 1;
}
batchedTokenResponses.push(otr);
}

return new BatchedTokenResponse(batchedTokenResponses);
return new GenericBatchTokenResponse(batchedTokenResponses);
}

serialize(): Uint8Array {
Expand All @@ -181,11 +213,6 @@ export class BatchedTokenResponse {
for (const tokenResponse of this.tokenResponses) {
const tokenResponseSerialized = tokenResponse.serialize();

const b = new ArrayBuffer(2);
new DataView(b).setUint16(0, tokenResponseSerialized.length);
output.push(b);
length += 2;

output.push(tokenResponseSerialized);
length += tokenResponseSerialized.length;
}
Expand Down Expand Up @@ -246,7 +273,7 @@ export class Issuer {
throw new Error('no issuer found provided the truncated token key id');
}

async issue(tokenRequests: BatchedTokenRequest): Promise<BatchedTokenResponse> {
async issue(tokenRequests: BatchedTokenRequest): Promise<GenericBatchTokenResponse> {
const tokenResponses: OptionalTokenResponse[] = [];
for (const tokenRequest of tokenRequests) {
try {
Expand All @@ -255,14 +282,15 @@ export class Issuer {
tokenRequest.truncatedTokenKeyId,
);
const response = (await issuer.issue(tokenRequest.tokenRequest)).serialize();
tokenResponses.push(new OptionalTokenResponse(response));
tokenResponses.push(new OptionalTokenResponse(TokenResponse.deserialize(response)));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
} catch (e) {
console.log(e);
tokenResponses.push(new OptionalTokenResponse(null));
}
}

return new BatchedTokenResponse(tokenResponses);
return new GenericBatchTokenResponse(tokenResponses);
}

tokenKeyIDs(tokenType: 1 | 2): Promise<Uint8Array[]> {
Expand Down Expand Up @@ -298,7 +326,7 @@ export class Client {
return new BatchedTokenRequest(tokenRequests);
}

deserializeTokenResponse(bytes: Uint8Array): BatchedTokenResponse {
return BatchedTokenResponse.deserialize(bytes);
deserializeTokenResponse(bytes: Uint8Array, types: (1 | 2)[]): GenericBatchTokenResponse {
return GenericBatchTokenResponse.deserialize(bytes, types);
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { convertEncToRSASSAPSS, convertRSASSAPSSToEnc } from './util.js';
export const util = { convertEncToRSASSAPSS, convertRSASSAPSSToEnc };
export * from './auth_scheme/private_token.js';
export * from './issuance.js';
export * as arbitraryBatched from './arbitrary_batched_token.js';
export * as genericBatched from './generic_batched_token.js';
export * as privateVerif from './priv_verif_token.js';
export * as publicVerif from './pub_verif_token.js';

Expand Down
4 changes: 2 additions & 2 deletions src/issuance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export enum MediaType {
PRIVATE_TOKEN_ISSUER_DIRECTORY = 'application/private-token-issuer-directory',
PRIVATE_TOKEN_REQUEST = 'application/private-token-request',
PRIVATE_TOKEN_RESPONSE = 'application/private-token-response',
ARBITRARY_BATCHED_TOKEN_REQUEST = 'application/private-token-arbitrary-batch-request',
ARBITRARY_BATCHED_TOKEN_RESPONSE = 'application/private-token-arbitrary-batch-response',
ARBITRARY_BATCHED_TOKEN_REQUEST = 'application/private-token-generic-batch-request',
ARBITRARY_BATCHED_TOKEN_RESPONSE = 'application/private-token-generic-batch-response',
}

// Issuer 'token-keys' object description'
Expand Down
4 changes: 4 additions & 0 deletions src/priv_verif_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ export class TokenResponse {
serialize(): Uint8Array {
return new Uint8Array(joinAll([this.evaluateMsg, this.evaluateProof]));
}

length(): number {
return this.evaluateMsg.length + this.evaluateProof.length;
}
}

export function verifyToken(token: Token, privateKeyIssuer: Uint8Array): Promise<boolean> {
Expand Down
6 changes: 5 additions & 1 deletion src/pub_verif_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export class TokenResponse {

constructor(public readonly blindSig: Uint8Array) {
if (blindSig.length !== BLIND_RSA.Nk) {
throw new Error('blind signature has invalid size');
throw new Error(`blind signature has invalid size: ${blindSig.length}`);
}
}

Expand All @@ -211,6 +211,10 @@ export class TokenResponse {
serialize(): Uint8Array {
return new Uint8Array(this.blindSig);
}

length(): number {
return this.blindSig.length;
}
}

abstract class PubliclyVerifiableIssuer {
Expand Down
Loading
Loading