mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-16 11:23:13 +00:00
184 lines
6.7 KiB
JavaScript
184 lines
6.7 KiB
JavaScript
import { DelegationStorage } from './storage';
|
|
import { DelegationCrypto } from './crypto';
|
|
export class DelegationManager {
|
|
constructor() {
|
|
this.cachedDelegation = null;
|
|
this.cachedAt = 0;
|
|
}
|
|
static getDurationHours(duration) {
|
|
return DelegationManager.DURATION_HOURS[duration];
|
|
}
|
|
// ============================================================================
|
|
// PUBLIC API
|
|
// ============================================================================
|
|
/**
|
|
* Create a delegation with cryptographic proof
|
|
*/
|
|
async delegate(address, walletType, duration = '7days', signFunction) {
|
|
try {
|
|
// Generate browser keypair
|
|
const keypair = DelegationCrypto.generateKeypair();
|
|
// Create expiry and nonce
|
|
const expiryTimestamp = Date.now() +
|
|
DelegationManager.getDurationHours(duration) * 60 * 60 * 1000;
|
|
const nonce = crypto.randomUUID();
|
|
// Create and sign authorization message
|
|
const authMessage = DelegationCrypto.createAuthMessage(keypair.publicKey, address, expiryTimestamp, nonce);
|
|
const walletSignature = await signFunction(authMessage);
|
|
// Store delegation
|
|
const delegationInfo = {
|
|
authMessage,
|
|
walletSignature,
|
|
expiryTimestamp,
|
|
walletAddress: address,
|
|
walletType,
|
|
browserPublicKey: keypair.publicKey,
|
|
browserPrivateKey: keypair.privateKey,
|
|
nonce,
|
|
};
|
|
await DelegationStorage.store(delegationInfo);
|
|
return true;
|
|
}
|
|
catch (error) {
|
|
console.error('Error creating delegation:', error);
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Sign a message with delegated key
|
|
*/
|
|
async signMessage(message) {
|
|
const now = Date.now();
|
|
if (!this.cachedDelegation ||
|
|
now - this.cachedAt > DelegationManager.CACHE_TTL_MS) {
|
|
this.cachedDelegation = await DelegationStorage.retrieve();
|
|
this.cachedAt = now;
|
|
}
|
|
const delegation = this.cachedDelegation;
|
|
if (!delegation || Date.now() >= delegation.expiryTimestamp) {
|
|
return null;
|
|
}
|
|
// Sign message content
|
|
const messageToSign = JSON.stringify({
|
|
...message,
|
|
signature: undefined,
|
|
browserPubKey: undefined,
|
|
delegationProof: undefined,
|
|
});
|
|
const signature = DelegationCrypto.signRaw(messageToSign, delegation.browserPrivateKey);
|
|
if (!signature)
|
|
return null;
|
|
return {
|
|
...message,
|
|
signature,
|
|
browserPubKey: delegation.browserPublicKey,
|
|
delegationProof: this.createProof(delegation),
|
|
};
|
|
}
|
|
/**
|
|
* Verify a signed message
|
|
*/
|
|
async verify(message) {
|
|
// Check required fields
|
|
if (!message.signature ||
|
|
!message.browserPubKey ||
|
|
!message.delegationProof ||
|
|
!message.author) {
|
|
return false;
|
|
}
|
|
// Verify message signature
|
|
const signedContent = JSON.stringify({
|
|
...message,
|
|
signature: undefined,
|
|
browserPubKey: undefined,
|
|
delegationProof: undefined,
|
|
});
|
|
if (!DelegationCrypto.verifyRaw(signedContent, message.signature, message.browserPubKey)) {
|
|
return false;
|
|
}
|
|
// Verify delegation proof
|
|
return await this.verifyProof(message.delegationProof, message.browserPubKey, message.author);
|
|
}
|
|
/**
|
|
* Get delegation status
|
|
*/
|
|
async getStatus(currentAddress, currentWalletType) {
|
|
const now = Date.now();
|
|
if (!this.cachedDelegation ||
|
|
now - this.cachedAt > DelegationManager.CACHE_TTL_MS) {
|
|
this.cachedDelegation = await DelegationStorage.retrieve();
|
|
this.cachedAt = now;
|
|
}
|
|
const delegation = this.cachedDelegation;
|
|
if (!delegation) {
|
|
return { hasDelegation: false, isValid: false };
|
|
}
|
|
const hasExpired = now >= delegation.expiryTimestamp;
|
|
const addressMatches = !currentAddress || delegation.walletAddress === currentAddress;
|
|
const walletTypeMatches = !currentWalletType || delegation.walletType === currentWalletType;
|
|
const isValid = !hasExpired && addressMatches && walletTypeMatches;
|
|
return {
|
|
hasDelegation: true,
|
|
isValid,
|
|
timeRemaining: isValid
|
|
? Math.max(0, delegation.expiryTimestamp - now)
|
|
: undefined,
|
|
publicKey: delegation.browserPublicKey,
|
|
address: delegation.walletAddress,
|
|
walletType: delegation.walletType,
|
|
proof: isValid ? this.createProof(delegation) : undefined,
|
|
};
|
|
}
|
|
/**
|
|
* Clear stored delegation
|
|
*/
|
|
async clear() {
|
|
await DelegationStorage.clear();
|
|
}
|
|
// ============================================================================
|
|
// PRIVATE HELPERS
|
|
// ============================================================================
|
|
/**
|
|
* Create delegation proof from stored info
|
|
*/
|
|
createProof(delegation) {
|
|
return {
|
|
authMessage: delegation.authMessage,
|
|
walletSignature: delegation.walletSignature,
|
|
expiryTimestamp: delegation.expiryTimestamp,
|
|
walletAddress: delegation.walletAddress,
|
|
walletType: delegation.walletType,
|
|
};
|
|
}
|
|
/**
|
|
* Verify delegation proof
|
|
*/
|
|
async verifyProof(proof, expectedBrowserKey, expectedWalletAddress) {
|
|
// Basic validation
|
|
if (!proof?.walletAddress ||
|
|
!proof?.authMessage ||
|
|
proof?.expiryTimestamp === undefined ||
|
|
proof.walletAddress !== expectedWalletAddress ||
|
|
Date.now() >= proof.expiryTimestamp) {
|
|
return false;
|
|
}
|
|
// Verify auth message format
|
|
if (!proof.authMessage.includes(expectedWalletAddress) ||
|
|
!proof.authMessage.includes(expectedBrowserKey) ||
|
|
!proof.authMessage.includes(proof.expiryTimestamp.toString())) {
|
|
return false;
|
|
}
|
|
// Verify wallet signature
|
|
return await DelegationCrypto.verifyWalletSignature(proof.authMessage, proof.walletSignature, proof.walletAddress, proof.walletType);
|
|
}
|
|
}
|
|
DelegationManager.CACHE_TTL_MS = 5 * 1000; // 5s to avoid hot-looping
|
|
DelegationManager.DURATION_HOURS = {
|
|
'7days': 24 * 7,
|
|
'30days': 24 * 30,
|
|
};
|
|
// Export singleton instance
|
|
export const delegationManager = new DelegationManager();
|
|
export * from './types';
|
|
export { DelegationStorage };
|
|
//# sourceMappingURL=index.js.map
|