@synet/keys
Version:
Zero-dependency, secure key generation library. Supports ed25519, x25519, secp256k1, RSA, and WireGuard keys.
446 lines (440 loc) • 15.9 kB
JavaScript
"use strict";
/**
* Signer Unit - Primary cryptographic signing unit
* [🔐] Self-contained cryptographic engine that knows how to sign
*
* Design principles:
* - Signer is the primary unit (holds private key securely)
* - Full Unit architecture with execute, teach, capabilities
* - Self-contained cryptographic operations (no external dependencies)
* - Can teach capabilities to Key units
*
* @author Synet Team
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Signer = void 0;
const unit_1 = require("@synet/unit");
const keys_1 = require("./keys");
const verify_1 = require("./verify");
const utils_1 = require("./utils");
const crypto = __importStar(require("node:crypto"));
const key_1 = require("./key");
/**
* Signer Unit - Primary unit for key generation and signing
* The source of truth for cryptographic operations
*/
class Signer extends unit_1.Unit {
constructor(props) {
super(props);
// Register capabilities
this._addCapability('sign', (...args) => {
// Handle both direct string and object with data property
const data = typeof args[0] === 'string' ? args[0] : args[0]?.data;
return this.sign(data);
});
this._addCapability('getPublicKey', () => this.getPublicKey());
this._addCapability('getPublicKeyHex', () => this.getPublicKeyHex());
this._addCapability('getPrivateKeyHex', () => this.getPrivateKeyHex());
this._addCapability('verify', (...args) => {
// Handle both direct strings and object with data/signature properties
if (typeof args[0] === 'string' && typeof args[1] === 'string') {
return this.verify(args[0], args[1]);
}
const obj = args[0];
return this.verify(obj.data, obj.signature);
});
this._addCapability('getAlgorithm', () => this.getAlgorithm());
this._addCapability('getKey', (...args) => this.getKey(args[0]));
this._addCapability('toJSON', () => this.toJSON());
}
/**
* Generate new signer with fresh key pair
*/
static generate(keyType, params = {}) {
try {
const keyPair = (0, keys_1.generateKeyPair)(keyType);
if (!keyPair || !keyPair.privateKey || !keyPair.publicKey) {
throw new Error('Failed to generate key pair');
}
const props = {
dna: (0, unit_1.createUnitSchema)({
id: 'signer',
version: '1.0.0'
}),
privateKeyPEM: keyPair.privateKey,
publicKeyPEM: keyPair.publicKey,
keyType,
keyId: (0, utils_1.createId)(),
metadata: params?.metadata || {},
created: new Date(),
secure: params?.secure !== undefined ? params.secure : false,
};
console.log(`[🔐] Generated new Signer with key type: ${keyType}, secure: ${props.secure} metadata: ${JSON.stringify(props.metadata)}`);
return new Signer(props);
}
catch (error) {
console.error('[🔐] Failed to generate signer:', error);
throw new Error('Failed to generate key pair');
}
}
/**
* Create signer from existing key pair
*/
static create(config) {
try {
if (!config.privateKeyPEM || !config.publicKeyPEM || !config.keyType) {
throw new Error('Invalid parameters, privateKeyPEM, publicKeyPEM, and keyType are required');
}
const props = {
dna: (0, unit_1.createUnitSchema)({
id: 'signer',
version: '1.0.0'
}),
privateKeyPEM: config.privateKeyPEM,
publicKeyPEM: config.publicKeyPEM,
keyType: config.keyType,
keyId: (0, utils_1.createId)(),
metadata: config.metadata || {},
created: new Date(),
secure: config.secure || true, // Use secure signing methods if specified
};
return new Signer(props);
}
catch (error) {
console.error('[🔐] Failed to create signer:', error);
throw error;
}
}
/**
* Create signer from existing key pair (compatibility method)
*/
static createFromKeyPair(privateKeyPEM, publicKeyPEM, keyType, metadata) {
return Signer.create({
privateKeyPEM,
publicKeyPEM,
keyType,
metadata
});
}
/**
* Create signer with external ISigner (edge case)
* For cases where signing logic is handled externally
*/
static createWithSigner(params) {
try {
// Create a Signer with external signing capability
// Note: Public key should be provided in metadata if needed
const publicKeyPEM = params.publicKeyPEM || '';
const keyType = params.keyType || 'ed25519';
const props = {
dna: (0, unit_1.createUnitSchema)({
id: 'signer',
version: '1.0.0'
}),
privateKeyPEM: '', // External signer handles signing
publicKeyPEM,
keyType,
keyId: (0, utils_1.createId)(),
metadata: params.metadata || {},
isigner: params.signer,
created: new Date(),
secure: true,
};
return new Signer(props);
}
catch (error) {
console.error('[🔐] Failed to create signer with external ISigner:', error);
return null;
}
}
// Unit implementation
whoami() {
return `[🔐] Signer Unit - Secure ${this.props.keyType} cryptographic engine (${this.props.keyId.slice(0, 8)})`;
}
capabilities() {
return this._getAllCapabilities();
}
help() {
console.log(`
[🔐] Signer Unit - Self-Contained Cryptographic Engine
Identity: ${this.whoami()}
Algorithm: ${this.props.keyType}
Core Capabilities:
- sign(data): Sign data with private key
- getPublicKey(): Get public key for sharing
- getPublicKeyHex(): Get public key in hex format
- getPrivateKeyHex(): Get private key in hex format (secure mode dependent)
- verify(data, signature): Verify signatures
- getAlgorithm(): Get signing algorithm
- getKey(): Get data needed to create associated Key unit
- toJSON(): Export metadata (no private key)
Unit Operations:
- execute(capability, ...args): Execute any capability
- teach(): Share all capabilities with other units
- capabilities(): List all available capabilities
- learn(capabilities): Absorb capabilities from other units
Security Contract:
- Private key never exposed outside unit
- Implements ISigner interface for external compatibility
- Can be used directly or through learned capabilities
Try me:
const signer = Signer.generate('ed25519', { name: 'my-signer' });
await signer.execute('sign', 'hello world');
const keyData = await signer.execute('getKey', { name: 'my-key' });
// Then use Key.createFromSigner(signer, keyData.meta) separately
const publicKey = await signer.execute('getPublicKey');
`);
}
teach() {
return {
unitId: this.dna.id,
capabilities: {
sign: (...args) => this.sign(args[0]),
getPublicKey: () => this.getPublicKey(),
getPublicKeyHex: () => this.getPublicKeyHex(),
getPrivateKeyHex: () => this.getPrivateKeyHex(),
verify: (...args) => this.verify(args[0], args[1]),
getAlgorithm: () => this.getAlgorithm(),
toJSON: () => this.toJSON(),
getKeyType: () => this.props.keyType,
}
};
}
// ISigner implementation
async sign(data) {
try {
// Use external signer if available, otherwise use native signing
if (this.props.isigner) {
return this.props.isigner.sign(data);
}
return this.performSigning(data, this.props.privateKeyPEM, this.props.keyType);
}
catch (error) {
throw new Error(`[🔐] Signing failed: ${error}`);
}
}
getPublicKey() {
return this.props.publicKeyPEM;
}
getAlgorithm() {
return this.props.keyType;
}
/**
* Create a Key unit from this Signer's key material
* The Key will learn signing capabilities from this Signer
*/
createKey() {
try {
const key = key_1.Key.create({
publicKeyPEM: this.props.publicKeyPEM,
keyType: this.props.keyType,
meta: { ...this.props.metadata }
});
if (key) {
// Teach the key our signing capabilities
const teaching = this.teach();
key.learn([teaching]);
}
return key;
}
catch (error) {
console.error('[🔐] Failed to create key:', error);
return null;
}
}
// Additional capabilities
async verify(data, signature) {
try {
return this.performVerification(data, signature, this.props.publicKeyPEM, this.props.keyType);
}
catch {
return false;
}
}
/**
* Get data needed to create associated Key unit
* Returns the data needed for Key.createFromSigner() to avoid circular dependency
*/
getKey(meta) {
return {
publicKeyPEM: this.props.publicKeyPEM,
keyType: this.props.keyType,
meta: { ...meta },
signer: this
};
}
/**
* Export signer metadata (excludes private key for security)
*/
toJSON() {
return {
id: this.props.keyId,
publicKeyPEM: this.props.publicKeyPEM,
type: this.props.keyType,
meta: this.props.meta,
canSign: true,
algorithm: this.props.keyType
};
}
// Getters for internal access
get id() {
return this.props.keyId;
}
get type() {
return this.props.keyType;
}
get metadata() {
return { ...this.props.metadata };
}
get privateKeyPEM() {
return this.props.secure ? '' : this.props.privateKeyPEM;
}
get publicKeyPEM() {
return this.props.publicKeyPEM;
}
get keyType() {
return this.props.keyType;
}
/**
* Convert key to PEM format for cryptographic operations
*/
convertToPEMFormat(keyData, keyType, isPublic = false) {
// Our keys are generated in PEM format by default, so they should already be PEM
if (keyData.includes('-----BEGIN')) {
return keyData;
}
// If we have a hex format key, we need to convert it
throw new Error(`Key format conversion needed for ${keyType} key. Expected PEM format but got hex.`);
}
/**
* Perform cryptographic signing based on key type
*/
performSigning(data, privateKey, keyType) {
if (!data || !privateKey) {
throw new Error('Invalid input: data and privateKey are required');
}
try {
switch (keyType) {
case 'ed25519':
return this.signEd25519(data, privateKey);
case 'rsa':
return this.signRSA(data, privateKey);
case 'secp256k1':
return this.signSecp256k1(data, privateKey);
case 'x25519':
throw new Error('X25519 is for key exchange, not signing');
case 'wireguard':
throw new Error('WireGuard keys are for VPN, not signing');
default:
throw new Error(`Unsupported key type for signing: ${keyType}`);
}
}
catch (error) {
throw new Error(`Signing failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Perform cryptographic verification based on key type
* Uses tested verification functions from verify.ts
*/
performVerification(data, signature, publicKey, keyType) {
return (0, verify_1.verifySignature)(data, signature, publicKey, keyType);
}
/**
* Sign data with Ed25519 key
*/
signEd25519(data, privateKey) {
const signature = crypto.sign(null, Buffer.from(data), {
key: privateKey,
format: 'pem',
});
const base64Signature = signature.toString('base64');
return (0, utils_1.base64ToBase64Url)(base64Signature);
}
/**
* Sign data with RSA key
*/
signRSA(data, privateKey) {
const sign = crypto.createSign('SHA256');
sign.update(data);
sign.end();
const base64Signature = sign.sign(privateKey, 'base64');
return (0, utils_1.base64ToBase64Url)(base64Signature);
}
/**
* Sign data with secp256k1 key
*/
signSecp256k1(data, privateKey) {
// For secp256k1, we use ECDSA with SHA256
const sign = crypto.createSign('SHA256');
sign.update(data);
sign.end();
const base64Signature = sign.sign(privateKey, 'base64');
return (0, utils_1.base64ToBase64Url)(base64Signature);
}
/**
* Get public key in hex format for DID generation
* This allows DID generation to work immediately with hex format
*/
getPublicKeyHex() {
try {
// Use the pemToHex utility from keys.ts
return (0, keys_1.pemToHex)(this.props.publicKeyPEM);
}
catch (error) {
console.error('[🔐] Failed to convert public key to hex:', error);
return null;
}
}
/**
* Get private key in hex format (respects security flag)
* Only returns private key if secure flag is false
*/
getPrivateKeyHex() {
if (this.props.secure) {
console.warn('Private key access denied - secure mode enabled');
return null;
}
try {
return (0, keys_1.pemPrivateKeyToHex)(this.props.privateKeyPEM);
}
catch (error) {
console.error('Failed to convert private key to hex:', error);
return null;
}
}
}
exports.Signer = Signer;