Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
eea30bb
ci: add unit-test job to CI workflow
kilo-code-bot[bot] Oct 15, 2026
d15e417
test: remove intentional failing test for CI verification
kilo-code-bot[bot] Oct 15, 2026
ce8c961
Merge pull request #2 from Adedoyinjr/kilo/adept-clover-dav
kilo-code-bot[bot] Oct 15, 2026
6885538
wip
kilo-code-bot[bot] Oct 17, 2026
d32c489
wip
kilo-code-bot[bot] Oct 17, 2026
edf94da
fix(auctions): secure bid recovery before commit and guard backup import
kilo-code-bot[bot] Oct 18, 2026
732dfba
fix(auction): track bid submission state for safe retries
Adedoyinjr Oct 22, 2026
e283b5b
Merge remote-tracking branch 'origin/develop' into kilo/autumn-tower-oc3
Adedoyinjr Oct 22, 2026
cd974b9
fix(auction): reconcile ambiguous bid submissions
Adedoyinjr Oct 22, 2026
4dd3c1e
test(auction): cover ambiguous bid reconciliation
Adedoyinjr Oct 22, 2026
93a4b6f
feat(auction): expose Soroban transaction status lookup
Adedoyinjr Oct 22, 2026
4ec1150
fix(auction): reconcile ambiguous bid submissions after reload
Adedoyinjr Oct 22, 2026
9ca76c3
fix(auction): persist bid transaction hash before broadcast
Adedoyinjr Oct 22, 2026
2e4a402
fix(auction): persist pending transaction hashes
Adedoyinjr Oct 22, 2026
0013b35
fix(auction): persist transaction hash before Soroban broadcast
Adedoyinjr Oct 22, 2026
1347807
test(auction): cover pre-broadcast hash persistence
Adedoyinjr Oct 22, 2026
736af17
test(auction): assert hash persists before broadcast
Adedoyinjr Oct 22, 2026
b4d1ac6
test(auction): assert final persisted committed state
Adedoyinjr Oct 22, 2026
287b1ce
fix(auction): wait for authoritative bid transaction status
Adedoyinjr Oct 23, 2026
c422461
test(auction): keep submitted bids unknown until confirmed
Adedoyinjr Oct 23, 2026
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
114 changes: 114 additions & 0 deletions src/lib/auctionEncryption.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, it } from 'vitest';
import {
AUCTION_VAULT_VERSION,
decrypt,
decryptBids,
encrypt,
encryptBids,
isValidVault,
UnsupportedVersionError,
validateVault,
type EncryptedBidVault,
} from '@/lib/auctionEncryption';

const PASSPHRASE = 'correct-horse-battery-staple';
const WRONG_PASSPHRASE = 'wrong-passphrase';
const ITERATIONS = 1000;

const bids: Record<
string,
{
name: string;
amountStroops: string;
depositStroops: string;
saltHex: string;
revealed: boolean;
}
> = {
nova: {
name: 'nova',
amountStroops: '25000000',
depositStroops: '25000000',
saltHex: 'ab'.repeat(32),
revealed: false,
},
};

function tamperBase64(vault: EncryptedBidVault, index: number): EncryptedBidVault {
const data = Buffer.from(vault.data, 'base64');
data[index] ^= 0xff;
return { ...vault, data: data.toString('base64') };
}

describe('auction encryption', () => {
it('encrypts and decrypts bid recovery material', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
expect(vault.version).toBe(AUCTION_VAULT_VERSION);
expect(vault.kdf.iterations).toBe(ITERATIONS);
expect(typeof vault.kdf.salt).toBe('string');
expect(typeof vault.cipher.iv).toBe('string');
expect(typeof vault.data).toBe('string');

const decrypted = await decryptBids(PASSPHRASE, vault);
expect(decrypted).toEqual(bids);
});

it('uses a fresh random salt and IV on every encryption', async () => {
const a = await encryptBids(PASSPHRASE, bids, ITERATIONS);
const b = await encryptBids(PASSPHRASE, bids, ITERATIONS);
expect(a.kdf.salt).not.toBe(b.kdf.salt);
expect(a.cipher.iv).not.toBe(b.cipher.iv);
expect(a.data).not.toBe(b.data);
});

it('rejects an incorrect passphrase', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
await expect(decryptBids(WRONG_PASSPHRASE, vault)).rejects.toThrow();
});

it('detects corrupted ciphertext (authentication failure)', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
const corrupted = tamperBase64(vault, 0);
await expect(decryptBids(PASSPHRASE, corrupted)).rejects.toThrow();
});

it('detects corrupted authentication tag', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
const data = Buffer.from(vault.data, 'base64');
// Flip a byte in the final 16-byte GCM tag.
data[data.length - 1] ^= 0xff;
const tampered = { ...vault, data: data.toString('base64') };
await expect(decryptBids(PASSPHRASE, tampered)).rejects.toThrow();
});

it('rejects unsupported payload versions', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
const bad = { ...vault, version: 99 };
expect(() => validateVault(bad)).toThrow(UnsupportedVersionError);
expect(isValidVault(bad)).toBe(false);
});

it('rejects invalid vault structures before decryption', async () => {
expect(() => validateVault(null)).toThrow();
expect(() => validateVault({})).toThrow();
expect(() => validateVault({ version: 1 })).toThrow();
expect(isValidVault({ version: 1 })).toBe(false);
expect(isValidVault(null)).toBe(false);
expect(isValidVault(undefined)).toBe(false);
});

it('rejects a missing or short authentication tag', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
const noTag = { ...vault, data: Buffer.from('aa', 'hex').toString('base64') };
expect(() => validateVault(noTag)).toThrow();
});

it('never exposes the passphrase or plaintext in the vault payload', async () => {
const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS);
const serialized = JSON.stringify(vault);
expect(serialized).not.toContain(PASSPHRASE);
expect(serialized).not.toContain(bids.nova.amountStroops);
expect(serialized).not.toContain(bids.nova.saltHex);
expect(serialized).not.toContain(bids.nova.depositStroops);
});
});
Loading
Loading