107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
/**
|
|
* Test-only fake `Authority` that signs with a fresh ed25519 keypair and
|
|
* exposes the matching `KeySlotPublic` for verification. Avoids the
|
|
* full-keychain construction machinery in `@suaveplan/crypto`'s test surface
|
|
* which would require a deep import on every primitive's tests.
|
|
*
|
|
* Only the fields actually touched by `verifyAuthoritySignature` are
|
|
* populated correctly (`algorithm` + `publicKey`). All other slot fields
|
|
* carry placeholder values that satisfy the type but are not checked at
|
|
* runtime by the verify path.
|
|
*/
|
|
import { ed25519 } from "@suaveplan/crypto-core/sign";
|
|
import type {
|
|
Authority,
|
|
DomainTag,
|
|
KeySlotPublic,
|
|
SlotId,
|
|
} from "@suaveplan/types";
|
|
|
|
/**
|
|
* Inline copy of `buildSigningInput` from `@suaveplan/crypto/domain-tags`.
|
|
* Reproduced here to keep this test helper free of any `@suaveplan/crypto`
|
|
* runtime dep (which would induce a cycle now that `@suaveplan/crypto`
|
|
* itself depends on this package via `chain-bundle`'s `EnvelopeSchema`
|
|
* adoption). Byte layout is identical: `tag-bytes ‖ 0x00 ‖ payload`.
|
|
*/
|
|
function buildSigningInput(domain: DomainTag, payload: Uint8Array): Uint8Array {
|
|
const tag = new TextEncoder().encode(domain);
|
|
const out = new Uint8Array(tag.length + 1 + payload.length);
|
|
out.set(tag, 0);
|
|
out[tag.length] = 0;
|
|
out.set(payload, tag.length + 1);
|
|
return out;
|
|
}
|
|
|
|
const ED25519_ALGORITHM_ID = 0;
|
|
|
|
export interface FakeAuthorityHandle {
|
|
readonly authority: Authority;
|
|
readonly publicSlot: KeySlotPublic<unknown>;
|
|
readonly secretKey: Uint8Array;
|
|
readonly publicKey: Uint8Array;
|
|
}
|
|
|
|
function makePlaceholderSlot(publicKey: Uint8Array): KeySlotPublic<unknown> {
|
|
return {
|
|
coordinate: {
|
|
application: "test" as never,
|
|
purpose: "sign" as never,
|
|
layoutId: 0 as never,
|
|
layoutFields: {} as never,
|
|
} as never,
|
|
algorithm: ED25519_ALGORITHM_ID as never,
|
|
notBefore: 0,
|
|
notAfter: Number.MAX_SAFE_INTEGER,
|
|
publicKey,
|
|
hasPublic: true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build a fake `Authority` backed by a fresh ed25519 keypair. The returned
|
|
* `publicSlot` matches the secretKey and is suitable for passing to
|
|
* `verifyAuthoritySignature` or any consumer of `KeySlotPublic`.
|
|
*/
|
|
export function createFakeAuthority(): FakeAuthorityHandle {
|
|
const keypair = ed25519.generateKeypair();
|
|
const secretKey = keypair.secretKey;
|
|
const publicKey = keypair.publicKey;
|
|
|
|
let disposed = false;
|
|
const publicSlot = makePlaceholderSlot(publicKey);
|
|
|
|
const authority: Authority = {
|
|
async sign(
|
|
domain: DomainTag,
|
|
_slot: SlotId,
|
|
payload: Uint8Array,
|
|
signal?: AbortSignal
|
|
): Promise<Uint8Array> {
|
|
if (disposed) {
|
|
throw new Error("authority disposed");
|
|
}
|
|
if (signal?.aborted === true) {
|
|
throw new Error("aborted");
|
|
}
|
|
const message = buildSigningInput(domain, payload);
|
|
return Promise.resolve(ed25519.sign(secretKey, message));
|
|
},
|
|
getPublicSlot(_slot: SlotId): KeySlotPublic<unknown> | undefined {
|
|
if (disposed) return undefined;
|
|
return publicSlot;
|
|
},
|
|
hasSlot(_slot: SlotId): boolean {
|
|
return !disposed;
|
|
},
|
|
dispose(): void {
|
|
disposed = true;
|
|
},
|
|
get disposed(): boolean {
|
|
return disposed;
|
|
},
|
|
};
|
|
|
|
return { authority, publicSlot, secretKey, publicKey };
|
|
}
|