nostr-nsec-seedphrase
Version:
A comprehensive TypeScript library for Nostr key management with BIP39 seed phrases, supporting both ESM and CommonJS. Implements NIP-01, NIP-06, NIP-19, and NIP-26 with key generation, event signing, bech32 encoding/decoding, and secure cryptographic ope
117 lines (116 loc) • 4.29 kB
JavaScript
/**
* @module nips/nip-26
* @description NIP-26 Delegated Event Signing implementation
* @see https://github.com/nostr-protocol/nips/blob/master/26.md
*/
import { schnorr } from "@noble/curves/secp256k1.js";
import { sha256 } from "@noble/hashes/sha2.js";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
import { logger } from "../utils/logger.js";
/**
* Creates a delegation token string
* @param conditions - Delegation conditions
* @returns Delegation token string
*/
function createDelegationString(delegator, delegatee, conditions) {
const parts = ["nostr", "delegation", delegator, delegatee];
if (conditions.kinds) {
parts.push(`kinds=${conditions.kinds.join(",")}`);
}
if (conditions.since) {
parts.push(`created_at>${conditions.since}`);
}
if (conditions.until) {
parts.push(`created_at<${conditions.until}`);
}
return parts.join(":");
}
/**
* Creates a delegation token
* @param delegatee - Public key of the delegatee
* @param conditions - Delegation conditions
* @param delegatorPrivateKey - Private key of the delegator
* @returns Delegation token
*/
export async function createDelegation(delegatee, conditions, delegatorPrivateKey) {
try {
// Get delegator's public key
const delegator = bytesToHex(schnorr.getPublicKey(hexToBytes(delegatorPrivateKey)));
// Create delegation string
const tokenString = createDelegationString(delegator, delegatee, conditions);
// Sign the token
const messageHash = sha256(new TextEncoder().encode(tokenString));
const signature = await schnorr.sign(messageHash, hexToBytes(delegatorPrivateKey));
logger.log("Created delegation token");
return {
delegator,
delegatee,
conditions,
signature: bytesToHex(signature),
};
}
catch (error) {
logger.error("Failed to create delegation token:", error?.toString());
throw error;
}
}
/**
* Verifies a delegation token
* @param token - The delegation token to verify
* @param now - Current Unix timestamp in seconds (optional, defaults to current time)
* @returns Result of the validation
*/
async function verifyDelegation(token, now) {
try {
const tokenObject = JSON.parse(token);
// Check conditions
if (now) {
if (tokenObject.conditions.since && now < tokenObject.conditions.since) {
return {
isValid: false,
error: "Event timestamp before delegation validity period",
};
}
if (tokenObject.conditions.until && now > tokenObject.conditions.until) {
return {
isValid: false,
error: "Event timestamp after delegation validity period",
};
}
}
// Verify signature
const tokenString = createDelegationString(tokenObject.delegator, tokenObject.delegatee, tokenObject.conditions);
const messageHash = sha256(new TextEncoder().encode(tokenString));
const isValid = await schnorr.verify(hexToBytes(tokenObject.signature), messageHash, hexToBytes(tokenObject.delegator));
return {
isValid,
error: isValid ? undefined : "Invalid delegation signature",
};
}
catch (error) {
logger.error("Failed to verify delegation:", error?.toString());
return {
isValid: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}
/**
* Checks if a delegation token is currently valid
* @param token - The delegation token to check
* @param now - Current Unix timestamp in seconds (optional, defaults to current time)
* @returns True if the delegation is valid
*/
export async function isValidDelegation(token, now) {
const result = await verifyDelegation(token, now);
return result.isValid;
}
/**
* Gets the expiration time of a delegation token
* @param token - Delegation token
* @returns Expiration timestamp or undefined if no expiration
*/
export function getDelegationExpiry(token) {
const tokenObject = JSON.parse(token);
return tokenObject.conditions.until;
}