Skip to content
Open
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
22 changes: 22 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "signify-ts-extern-kms-example",
"version": "1.0.0",
"description": "Example project for Extern (Incept & Rotate)",
"type": "module",
"scripts": {
"create": "tsx src/extern_createAccount.ts",
"rotate": "tsx src/extern_rotation.ts"
},
"dependencies": {
"@aws-sdk/client-kms": "^3.998.0",
"signify-ts": "^0.3.0-rc2"
},
"overrides": {
"libsodium-wrappers-sumo": "0.7.15"
},
"devDependencies": {
"@types/node": "^25.0.3",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
}
63 changes: 63 additions & 0 deletions examples/src/AwsKmsModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Algos, Matter, MtrDex, Diger, Siger, Signer } from "signify-ts";
import { KMSClient, GetPublicKeyCommand, SignCommand, SignCommandInput } from "@aws-sdk/client-kms";

/* =================================================
* AWS KMS External Module Implementation
* -------------------------------------------------
* This strictly implements the IdentifierManager interface.
* You can swap the inner logic with `@google-cloud/kms` to test on GCP.
* ================================================= */
export class AwsKmsModule {
algo: Algos = Algos.extern;
signers: Signer[] = []; // KMS handles signing, so local signers remain empty
private kms: KMSClient;
private keyId: string;

constructor(pidx: number, args: any, region: string, keyId: string) {
this.kms = new KMSClient({ region });
this.keyId = keyId;
}

// This data is passed to KERIA and stored in the DB (fixes the 500 error)
params(): any {
return { extern_type: "aws_kms" };
}

// Fetches the public key from AWS KMS and formats it to qb64
async getPubQb64(): Promise<string> {
const cmd = new GetPublicKeyCommand({ KeyId: this.keyId });
const res = await this.kms.send(cmd);
if (!res.PublicKey) throw new Error("PublicKey not found in KMS");

// Extract raw 32 bytes from AWS Ed25519 DER format
const raw32 = Buffer.from(res.PublicKey).slice(-32);
return new Matter({ raw: raw32, code: MtrDex.Ed25519 }).qb64;
}

async incept(transferable: boolean): Promise<[string[], string[]]> {
const pubQb64 = await this.getPubQb64();
const nextDigQb64 = new Diger({ code: MtrDex.Blake3_256 }, new Matter({ qb64: pubQb64 }).qb64b).qb64;

return [[pubQb64], [nextDigQb64]];
}

async rotate(ncodes: string[], transferable: boolean): Promise<[string[], string[]]> {
return this.incept(transferable);
}

// Delegates the payload signing to AWS KMS
async sign(ser: Uint8Array, indexed: boolean = true): Promise<string[]> {
const input: SignCommandInput = {
KeyId: this.keyId,
Message: ser,
MessageType: "RAW",
SigningAlgorithm: "ED25519_SHA_512", // Required for Ed25519 in AWS SDK v3
};
const command = new SignCommand(input);
const response = await this.kms.send(command);
if (!response.Signature) throw new Error("Signature generation failed");

const sigBytes = Buffer.from(response.Signature);
return [new Siger({ raw: sigBytes, index: 0 }).qb64];
}
}
139 changes: 139 additions & 0 deletions examples/src/extern_createAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import dotenv from 'dotenv';
import { Tier, Algos, ready, SignifyClient, ExternalModule } from 'signify-ts';
import { AwsKmsModule } from './AwsKmsModule.js';

dotenv.config();

/* =================================================
* Minimal Helpers for Standalone Execution
* ================================================= */
async function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}

async function waitOperation(client: SignifyClient, op: any): Promise<any> {
let cur = op;
for (let i = 0; i < 30; i++) {
if (cur?.done) {
try {
await client.operations().delete(op.name);
} catch {}
return cur.response;
}
await sleep(1000);
cur = await client.operations().get(op.name);
}
throw new Error(`Operation timed out: ${op.name}`);
}

async function connectClient(
url: string,
bootUrl: string,
bran: string,
modules: any[]
) {
await ready();
const client = new SignifyClient(url, bran, Tier.low, bootUrl, modules);

try {
await client.connect();
} catch {
await client.boot();
await sleep(500);
await client.connect();
}
return client;
}

/* =================================================
* Main Execution Script (Testing PR #415 FIX)
* ================================================= */
async function main() {
console.log('\n🚀 EXTERN AID STANDALONE DEMO (CREATE ACCOUNT)');

const KERIA_URL = process.env.KERIA_URL || '';
const KERIA_PORT = process.env.KERIA_PORT || '3901';
const BOOT_PORT = process.env.BOOT_PORT || '3903';
const BRAN = process.env.TEST_BRAN || '';
const ALIAS = process.env.TEST_ALIAS || '';
const AWS_REGION = process.env.AWS_REGION || '';
const AWS_KEY_ID = process.env.AWS_KMS_KEY_ID || '';

await ready();

const modules: ExternalModule[] = [
{
type: 'aws_kms',
name: 'AwsKmsModule',
module: class {
constructor(pidx: number, args: any) {
return new AwsKmsModule(
pidx,
args,
AWS_REGION,
AWS_KEY_ID
) as any;
}
} as any,
},
];

console.log('\n[SETUP] Connecting to KERIA...');
const client = await connectClient(
`${KERIA_URL}:${KERIA_PORT}`,
`${KERIA_URL}:${BOOT_PORT}`,
BRAN,
modules
);
console.log('✅ Client connected.');

const identifiers = client.identifiers();
let prefix: string | undefined;

console.log(`\n[STEP 1] Ensure extern AID with alias: ${ALIAS}`);
try {
const hab = await identifiers.get(ALIAS);
prefix = hab.prefix || hab.state?.i;
console.log(`ℹ️ AID already exists: ${prefix}`);
} catch {
console.log('➡️ Creating new extern AID...');
const createRes = await identifiers.create(ALIAS, {
algo: Algos.extern,
extern_type: 'aws_kms',
transferable: true,
wits: [],
toad: 0,
});

const op = await createRes.op();
const result = await waitOperation(client, op);
prefix = result?.i || result?.pre;
console.log(`✅ AWS KMS AID created successfully: ${prefix}`);
}

console.log(
`\n[STEP 2] Verifying metadata persistence (The 500 Error Fix)`
);
const aidInfo = await identifiers.get(ALIAS);

console.log('-----------------------------------------');
console.log(JSON.stringify(aidInfo, null, 2));
console.log('-----------------------------------------');

if (aidInfo.extern && aidInfo.extern.extern_type === 'aws_kms') {
console.log(
"✅ SUCCESS: The 'extern' metadata is correctly persisted and retrieved without throwing a 500 error!"
);
} else {
console.error(
"❌ FAILED: The 'extern' metadata is missing from the DB response."
);
}
}

if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((e) => {
console.error('\n💥 FATAL ERROR:', e);
process.exit(1);
});
}
Loading