120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
/**
|
|
* Test-only DI bindings for `@suaveplan/bitpacker-crypto` primitives.
|
|
*
|
|
* Implemented directly against the lower-level `@suaveplan/crypto-core`
|
|
* (sign/verify) and `@suaveplan/crypto-cipher` (AEAD) packages so this
|
|
* test surface has NO dependency on `@suaveplan/crypto`. That breaks an
|
|
* otherwise-circular dep chain: `crypto` may depend on `bitpacker-crypto`
|
|
* (e.g. for `EnvelopeSchema` in `chain-bundle`) without inducing a cycle
|
|
* via this test helper.
|
|
*
|
|
* Production callers should still wire `verifyAuthoritySignature` from
|
|
* `@suaveplan/crypto/authority` and `aeadEncrypt` / `aeadDecrypt` from
|
|
* `@suaveplan/crypto` — those are the canonical implementations. This
|
|
* file mirrors their shape for test purposes only.
|
|
*/
|
|
|
|
import * as aesGcm from "@suaveplan/crypto-cipher/aes-256-gcm";
|
|
import * as chacha20 from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
|
import * as xchacha20 from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
|
import { ed25519 } from "@suaveplan/crypto-core/sign";
|
|
import type {
|
|
AeadAlgorithm,
|
|
AeadOps,
|
|
AeadOpsOptions,
|
|
AuthorityVerifier,
|
|
DomainTag,
|
|
} from "@suaveplan/types";
|
|
|
|
const ED25519_ALGORITHM_ID = 0;
|
|
|
|
/**
|
|
* Inline copy of `buildSigningInput` from `@suaveplan/crypto/domain-tags`.
|
|
* Reproduced here to keep this test helper free of any `@suaveplan/crypto`
|
|
* runtime dep. The 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;
|
|
}
|
|
|
|
/**
|
|
* Test verifier — mirrors `verifyAuthoritySignature` from
|
|
* `@suaveplan/crypto/authority` byte-for-byte. Only ed25519 is supported
|
|
* (matches the canonical impl); other algorithms throw to preserve the
|
|
* "structural error → swallow as false" convention used by primitives.
|
|
*/
|
|
export const cryptoVerifier: AuthorityVerifier = (
|
|
domain,
|
|
publicSlot,
|
|
payload,
|
|
signature
|
|
) => {
|
|
if (publicSlot.algorithm !== ED25519_ALGORITHM_ID) {
|
|
throw new Error(
|
|
`cryptoVerifier: unsupported algorithm ${publicSlot.algorithm}`
|
|
);
|
|
}
|
|
const message = buildSigningInput(domain, payload);
|
|
return ed25519.verify(publicSlot.publicKey, signature, message);
|
|
};
|
|
|
|
function encrypt(
|
|
algorithm: AeadAlgorithm,
|
|
key: Uint8Array,
|
|
plaintext: Uint8Array,
|
|
options: AeadOpsOptions
|
|
): Uint8Array {
|
|
switch (algorithm) {
|
|
case "AES-256-GCM":
|
|
return aesGcm.encrypt(key, options.nonce, plaintext, options.aad);
|
|
case "ChaCha20-Poly1305":
|
|
return chacha20.encrypt(key, options.nonce, plaintext, options.aad);
|
|
case "XChaCha20-Poly1305":
|
|
return xchacha20.encrypt(
|
|
key,
|
|
options.nonce,
|
|
plaintext,
|
|
options.aad
|
|
);
|
|
}
|
|
}
|
|
|
|
function decrypt(
|
|
algorithm: AeadAlgorithm,
|
|
key: Uint8Array,
|
|
ciphertext: Uint8Array,
|
|
options: AeadOpsOptions
|
|
): Uint8Array {
|
|
switch (algorithm) {
|
|
case "AES-256-GCM":
|
|
return aesGcm.decrypt(key, options.nonce, ciphertext, options.aad);
|
|
case "ChaCha20-Poly1305":
|
|
return chacha20.decrypt(
|
|
key,
|
|
options.nonce,
|
|
ciphertext,
|
|
options.aad
|
|
);
|
|
case "XChaCha20-Poly1305":
|
|
return xchacha20.decrypt(
|
|
key,
|
|
options.nonce,
|
|
ciphertext,
|
|
options.aad
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test AEAD bundle — dispatches to the per-algorithm functions exposed by
|
|
* `@suaveplan/crypto-cipher`. Behaviorally identical to the canonical
|
|
* `{ encrypt: aeadEncrypt, decrypt: aeadDecrypt }` shape from
|
|
* `@suaveplan/crypto`.
|
|
*/
|
|
export const cryptoAead: AeadOps = { encrypt, decrypt };
|