@shard-auth/client
Version:
Next-generation API authentication without secret keys - MPC-based authentication with FROST threshold signatures
195 lines • 7.65 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SignatureAPIServer = void 0;
const signature_1 = require("../frost/signature");
const frost_api_signature_1 = require("./frost-api-signature");
const crypto_1 = __importDefault(require("crypto"));
class SignatureAPIServer {
sessionManager;
serverShare;
constructor(sessionManager, serverShare) {
this.sessionManager = sessionManager;
this.serverShare = serverShare;
}
async handleSessionStart(request) {
// リクエスト検証
this.validateSessionRequest(request);
// タイムスタンプ検証
this.validateTimestamp(request.timestamp);
// サーバー側のノンス生成
const serverNonce = (0, signature_1.generateNonce)();
const serverNonceCommitment = (0, signature_1.computeNonceCommitment)(serverNonce);
// セッション作成
const session = this.sessionManager.createSession({
method: request.method,
path: request.path,
bodyHash: request.bodyHash,
timestamp: request.timestamp
}, request.clientNonceCommitment, serverNonce, serverNonceCommitment);
return {
sessionId: session.sessionId,
serverNonceCommitment: serverNonceCommitment,
expiresAt: session.expiresAt.toISOString()
};
}
async handleCompleteSignature(request, publicKey) {
// セッション検証
const session = this.sessionManager.getSession(request.sessionId);
if (!session) {
throw new Error('Session not found');
}
// 期限切れチェック
if (new Date() > session.expiresAt) {
throw new Error('Session expired');
}
if (session.used) {
throw new Error('Session already used');
}
// メッセージ生成
const message = this.createSigningMessage(session.requestDetails);
// サーバー側の部分署名を生成
const { partialSignature: serverPartialSig, groupCommitment } = await (0, frost_api_signature_1.createAPIPartialSignature)(this.serverShare, message, session.serverNonce, session.clientNonceCommitment, publicKey);
// クライアントとサーバーの部分署名を結合
const signature = await (0, frost_api_signature_1.combineAPIPartialSignatures)(request.partialSignature, serverPartialSig, groupCommitment);
// セッションを使用済みにマーク
this.sessionManager.markSessionUsed(session.sessionId);
// Authorizationヘッダー生成
const authHeader = this.createAuthorizationHeader(publicKey, signature, session.requestDetails.timestamp);
return {
signature: {
r: signature.r.toString('base64'),
s: signature.s.toString('base64')
},
authorizationHeader: authHeader
};
}
async verifySignature(authorizationHeader, requestDetails) {
try {
// ヘッダーパース
const parsed = this.parseAuthorizationHeader(authorizationHeader);
if (!parsed) {
return {
isValid: false,
error: 'Invalid authorization header format'
};
}
// タイムスタンプ検証
try {
this.validateTimestamp(parsed.timestamp);
}
catch (error) {
return {
isValid: false,
error: 'Timestamp expired'
};
}
// メッセージ生成
const message = this.createSigningMessage({
...requestDetails,
timestamp: parsed.timestamp
});
// 署名検証
const publicKey = Buffer.from(parsed.publicKey, 'base64');
const r = Buffer.from(parsed.signature.r, 'base64');
const s = Buffer.from(parsed.signature.s, 'base64');
// FROSTSignatureオブジェクトとして検証
const signature = {
r,
s,
async verify(msg, pubKey) {
// frost-api-signatureの検証ロジックを使用
const { verifyAPISignature } = require('./frost-api-signature');
const sBigInt = BigInt('0x' + s.toString('hex'));
return verifyAPISignature(r, sBigInt, msg, pubKey);
}
};
const isValid = await signature.verify(message, publicKey);
return {
isValid,
publicKey: parsed.publicKey,
error: isValid ? undefined : 'Invalid signature'
};
}
catch (error) {
return {
isValid: false,
error: 'Verification error: ' + error.message
};
}
}
validateSessionRequest(request) {
if (!request.method || !request.path || !request.bodyHash || !request.timestamp) {
throw new Error('Invalid request');
}
if (!request.clientNonceCommitment) {
throw new Error('Client nonce commitment required');
}
}
validateTimestamp(timestamp) {
const requestTime = new Date(timestamp).getTime();
const now = Date.now();
const fiveMinutes = 5 * 60 * 1000;
if (now - requestTime > fiveMinutes) {
throw new Error('Timestamp too old');
}
}
createSigningMessage(details) {
const parts = [
details.method,
details.path,
details.bodyHash.toString('base64'),
details.timestamp
];
const combined = parts.join('|');
return crypto_1.default.createHash('sha256').update(combined).digest();
}
createAuthorizationHeader(publicKey, signature, timestamp) {
const pk = publicKey.toString('base64');
const sig = `${signature.r.toString('base64')}.${signature.s.toString('base64')}`;
return `ShardAuth pk=${pk},sig=${sig},ts=${timestamp}`;
}
parseAuthorizationHeader(header) {
if (!header || !header.startsWith('ShardAuth ')) {
return null;
}
const parts = header.substring(10).split(',');
if (parts.length !== 3) {
return null;
}
const result = {};
for (const part of parts) {
const [key, value] = part.split('=');
if (!key || !value)
return null;
switch (key) {
case 'pk':
result.publicKey = value;
break;
case 'sig':
const [r, s] = value.split('.');
if (!r || !s)
return null;
result.signature = { r, s };
break;
case 'ts':
result.timestamp = value;
break;
default:
return null;
}
}
if (!result.publicKey || !result.signature || !result.timestamp) {
return null;
}
return result;
}
// テスト用メソッド
getEndpoint(path) {
return `/api/v1${path}`;
}
}
exports.SignatureAPIServer = SignatureAPIServer;
//# sourceMappingURL=signature-server.js.map