Initial Commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user