semnet-snap-protocol
Version:
TypeScript reference implementation of the SNAP Protocol v1.1 - Agent Internet Edition
193 lines • 5.31 kB
JavaScript
import nacl from 'tweetnacl';
const { sign, verify, box, randomBytes } = nacl;
import { nanoid } from 'nanoid';
/**
* Generate a new agent identity with Ed25519 keypair
*/
export function generateAgentIdentity() {
const keypair = sign.keyPair();
const uuid = generateUUID();
return {
identity: {
id: `snap:agent:${uuid}`,
publicKey: encodeBase64(keypair.publicKey)
},
privateKey: keypair.secretKey,
publicKey: keypair.publicKey
};
}
/**
* Generate a UUID v4
*/
export function generateUUID() {
const bytes = randomBytes(16);
// Set version (4) and variant bits
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
// Format as UUID string
const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
return [
hex.slice(0, 8),
hex.slice(8, 12),
hex.slice(12, 16),
hex.slice(16, 20),
hex.slice(20, 32)
].join('-');
}
/**
* Generate a unique message ID
*/
export function generateMessageId() {
return `msg_${nanoid()}`;
}
/**
* Generate a unique context ID
*/
export function generateContextId() {
return `ctx_${nanoid()}`;
}
/**
* Sign a message with Ed25519 private key
*/
export function signMessage(message, privateKey) {
const messageBytes = new TextEncoder().encode(message);
const signature = sign.detached(messageBytes, privateKey);
return encodeBase64(signature);
}
/**
* Verify a message signature with Ed25519 public key
*/
export function verifySignature(message, signature, publicKey) {
try {
const messageBytes = new TextEncoder().encode(message);
const signatureBytes = decodeBase64(signature);
const publicKeyBytes = decodeBase64(publicKey);
return sign.detached.verify(messageBytes, signatureBytes, publicKeyBytes);
}
catch {
return false;
}
}
/**
* Validate agent ID format
*/
export function validateAgentId(id) {
const regex = /^snap:agent:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
return regex.test(id);
}
/**
* Extract UUID from agent ID
*/
export function extractUUID(agentId) {
const match = agentId.match(/^snap:agent:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/);
return match ? match[1] : null;
}
/**
* Encode bytes to base64
*/
export function encodeBase64(bytes) {
return Buffer.from(bytes).toString('base64');
}
/**
* Decode base64 to bytes
*/
export function decodeBase64(base64) {
return new Uint8Array(Buffer.from(base64, 'base64'));
}
/**
* Hash content using SHA-256
*/
export async function hashContent(content) {
const encoder = new TextEncoder();
const data = encoder.encode(content);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = new Uint8Array(hashBuffer);
return encodeBase64(hashArray);
}
/**
* Generate a secure random string
*/
export function generateSecureRandom(length = 32) {
const bytes = randomBytes(length);
return encodeBase64(bytes).slice(0, length);
}
/**
* Create a canonical string representation of an object for signing
*/
export function canonicalizeObject(obj) {
// Sort keys recursively and stringify
const sortedObj = sortKeysRecursive(obj);
return JSON.stringify(sortedObj);
}
function sortKeysRecursive(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(sortKeysRecursive);
}
const sorted = {};
const keys = Object.keys(obj).sort();
for (const key of keys) {
sorted[key] = sortKeysRecursive(obj[key]);
}
return sorted;
}
/**
* Agent identity utilities
*/
export class AgentIdentity {
identity;
privateKey;
publicKey;
constructor(identity, privateKey, publicKey) {
this.identity = identity;
this.privateKey = privateKey;
this.publicKey = publicKey;
}
/**
* Sign a message with this agent's private key
*/
sign(message) {
return signMessage(message, this.privateKey);
}
/**
* Sign an object by creating canonical representation
*/
signObject(obj) {
const canonical = canonicalizeObject(obj);
return this.sign(canonical);
}
/**
* Export public identity (safe to share)
*/
exportPublic() {
return {
...this.identity,
publicKey: encodeBase64(this.publicKey)
};
}
/**
* Export private key (keep secure!)
*/
exportPrivateKey() {
return encodeBase64(this.privateKey);
}
/**
* Import agent identity from private key
*/
static fromPrivateKey(identity, privateKeyBase64) {
const privateKey = decodeBase64(privateKeyBase64);
// Extract public key from private key (Ed25519 property)
const publicKey = privateKey.slice(32);
return new AgentIdentity(identity, privateKey, publicKey);
}
/**
* Generate new agent identity
*/
static generate() {
const { identity, privateKey, publicKey } = generateAgentIdentity();
return new AgentIdentity(identity, privateKey, publicKey);
}
}
//# sourceMappingURL=identity.js.map