کاوشی عمیق در استفاده از تایپ استاتیک TypeScript برای ساخت سیستمهای امضای دیجیتال قوی و امن. یاد بگیرید چگونه از آسیبپذیریها جلوگیری و احراز هویت را با الگوهای نوع-ایمن بهبود بخشید.
امضاهای دیجیتال TypeScript: راهنمای جامع برای ایمنی نوع احراز هویت
در اقتصاد جهانی فوق متصل ما، اعتماد دیجیتال ارز نهایی است. از تراکنشهای مالی گرفته تا ارتباطات امن و توافقنامههای الزامآور قانونی، نیاز به هویت دیجیتالی قابل تأیید و ضد دستکاری هرگز حیاتیتر نبوده است. در قلب این اعتماد دیجیتال، امضای دیجیتال قرار دارد - یک شگفتی رمزنگاری که احراز هویت، یکپارچگی و عدم انکار را فراهم میکند. با این حال، پیادهسازی این ابتداییترین رمزنگاری پیچیده با خطر همراه است. یک متغیر اشتباه، یک نوع داده نادرست یا یک خطای منطقی ظریف میتواند به آرامی کل مدل امنیتی را تضعیف کند و آسیبپذیریهای فاجعهباری ایجاد کند.
برای توسعهدهندگانی که در اکوسیستم جاوااسکریپت کار میکنند، این چالش تشدید میشود. ماهیت پویا و آزادانه تایپ شده زبان، انعطافپذیری باورنکردنی را ارائه میدهد اما دری را به روی دستهای از اشکالات باز میکند که به ویژه در زمینه امنیتی خطرناک هستند. هنگامی که کلیدهای رمزنگاری حساس یا بافرهای داده را منتقل میکنید، یک اجبار نوع ساده میتواند تفاوت بین یک امضای امن و یک امضای بیفایده باشد. اینجاست که TypeScript نه تنها به عنوان یک راحتی برای توسعهدهنده، بلکه به عنوان یک ابزار امنیتی حیاتی ظاهر میشود.
این راهنمای جامع مفهوم ایمنی نوع احراز هویت را بررسی میکند. ما به این موضوع میپردازیم که چگونه میتوان از سیستم نوع استاتیک TypeScript برای تقویت پیادهسازیهای امضای دیجیتال استفاده کرد و کد شما را از یک میدان مین از خطاهای احتمالی زمان اجرا به یک سنگر از تضمینهای امنیتی زمان کامپایل تبدیل کرد. ما از مفاهیم بنیادین به مثالهای کد عملی و دنیای واقعی میرویم و نشان میدهیم که چگونه سیستمهای احراز هویت امنتر، قابل نگهداریتر و بهطور قابلتوجهی امنتری را برای مخاطبان جهانی بسازیم.
مبانی: یک مرور سریع در مورد امضاهای دیجیتال
قبل از اینکه به نقش TypeScript بپردازیم، اجازه دهید یک درک روشن و مشترک از ماهیت امضای دیجیتال و نحوه عملکرد آن ایجاد کنیم. این چیزی بیش از یک تصویر اسکن شده از یک امضای دستنویس است. این یک مکانیزم رمزنگاری قدرتمند است که بر سه رکن اصلی ساخته شده است.
رکن 1: هش کردن برای یکپارچگی دادهها
تصور کنید که یک سند دارید. برای اطمینان از اینکه هیچ کس یک حرف را بدون اطلاع شما تغییر نمیدهد، آن را از طریق یک الگوریتم هش (مانند SHA-256) اجرا میکنید. این الگوریتم یک رشته کاراکتر منحصر به فرد و با اندازه ثابت به نام هش یا خلاصه پیام تولید میکند. این یک فرآیند یکطرفه است؛ شما نمیتوانید سند اصلی را از هش برگردانید. مهمتر از همه، اگر حتی یک بیت از سند اصلی تغییر کند، هش حاصل کاملاً متفاوت خواهد بود. این یکپارچگی دادهها را فراهم میکند.
رکن 2: رمزگذاری نامتقارن برای اصالت و عدم انکار
اینجاست که جادو اتفاق میافتد. رمزگذاری نامتقارن، که به عنوان رمزنگاری کلید عمومی نیز شناخته میشود، شامل یک جفت کلید مرتبط ریاضی برای هر کاربر است:
- یک کلید خصوصی: توسط مالک کاملاً محرمانه نگه داشته میشود. این برای امضا کردن استفاده میشود.
- یک کلید عمومی: آزادانه با جهان به اشتراک گذاشته میشود. این برای تأیید استفاده میشود.
هر چیزی که با کلید خصوصی رمزگذاری شده باشد، فقط با کلید عمومی مربوطه قابل رمزگشایی است. این رابطه بنیاد اعتماد است.
فرآیند امضا و تأیید
بیایید همه چیز را در یک گردش کار ساده به هم گره بزنیم:
- امضا:
- آلیس میخواهد یک قرارداد امضا شده را برای باب ارسال کند.
- او ابتدا یک هش از سند قرارداد ایجاد میکند.
- سپس از کلید خصوصی خود برای رمزگذاری این هش استفاده میکند. این هش رمزگذاری شده همان امضای دیجیتال است.
- آلیس سند قرارداد اصلی را همراه با امضای دیجیتال خود برای باب ارسال میکند.
- تأیید:
- باب قرارداد و امضا را دریافت میکند.
- او سند قرارداد دریافت شده را میگیرد و هش آن را با استفاده از همان الگوریتم هش که آلیس استفاده کرده است، محاسبه میکند.
- سپس از کلید عمومی آلیس (که میتواند از یک منبع معتبر دریافت کند) برای رمزگشایی امضایی که او ارسال کرده است استفاده میکند. این هش اصلی را که او محاسبه کرده است، نشان میدهد.
- باب دو هش را مقایسه میکند: هشی که خودش محاسبه کرده است و هشی که از امضا رمزگشایی کرده است.
اگر هشها مطابقت داشته باشند، باب میتواند از سه چیز اطمینان داشته باشد:
- احراز هویت: فقط آلیس، صاحب کلید خصوصی، میتوانست امضایی ایجاد کند که کلید عمومی او بتواند آن را رمزگشایی کند.
- یکپارچگی: سند در حین انتقال تغییر نکرده است، زیرا هش محاسبه شده او با هش موجود در امضا مطابقت دارد.
- عدم انکار: آلیس نمیتواند بعداً امضای سند را انکار کند، زیرا فقط او کلید خصوصی مورد نیاز برای ایجاد امضا را دارد.
چالش جاوااسکریپت: جایی که آسیبپذیریهای مربوط به نوع پنهان میشوند
در یک دنیای عالی، فرآیند بالا بیعیب و نقص است. در دنیای واقعی توسعه نرمافزار، بهویژه با جاوااسکریپت ساده، اشتباهات ظریف میتوانند حفرههای امنیتی بزرگی ایجاد کنند.
یک تابع کتابخانه رمزنگاری معمولی در Node.js را در نظر بگیرید:
// یک تابع امضای جاوااسکریپت ساده فرضی
function createSignature(data, privateKey, algorithm) {
const sign = crypto.createSign(algorithm);
sign.update(data);
sign.end();
const signature = sign.sign(privateKey, 'base64');
return signature;
}
این به اندازه کافی ساده به نظر میرسد، اما چه چیزی ممکن است اشتباه شود؟
- نوع داده نادرست برای `data`: متد `sign.update()` اغلب انتظار یک `string` یا یک `Buffer` را دارد. اگر یک توسعهدهنده به طور تصادفی یک عدد (`12345`) یا یک شی (`{ id: 12345 }`) را منتقل کند، جاوااسکریپت ممکن است آن را به طور ضمنی به یک رشته (`"12345"` یا `"[object Object]"`) تبدیل کند. امضا بدون خطا تولید میشود، اما برای دادههای اشتباه اساسی خواهد بود. تأیید سپس با شکست مواجه میشود و منجر به اشکالات ناامیدکننده و دشوار در تشخیص میشود.
- فرمتهای کلیدی اشتباه مدیریت شده: متد `sign.sign()` در مورد قالب `privateKey` سختگیر است. میتواند یک رشته در قالب PEM، یک `KeyObject` یا یک `Buffer` باشد. ارسال قالب اشتباه ممکن است باعث خرابی زمان اجرا یا، بدتر از آن، یک شکست خاموش شود که در آن یک امضای نامعتبر تولید میشود.
- مقادیر `null` یا `undefined`: اگر `privateKey` به دلیل یک خطای جستجوی پایگاه داده `undefined` باشد، چه اتفاقی میافتد؟ برنامه در زمان اجرا خراب میشود، به طور بالقوه به روشی که وضعیت سیستم داخلی را نشان میدهد یا یک آسیبپذیری انکار سرویس ایجاد میکند.
- عدم تطابق الگوریتم: اگر تابع امضا از `'sha256'` استفاده کند اما تأیید کننده انتظار امضایی را داشته باشد که با `'sha512'` تولید شده است، تأیید همیشه با شکست مواجه میشود. بدون اعمال سیستم نوع، این فقط به نظم و انضباط توسعهدهنده و مستندات متکی است.
اینها فقط خطاهای برنامهنویسی نیستند. آنها نقصهای امنیتی هستند. یک امضای نادرست تولید شده میتواند منجر به رد شدن تراکنشهای معتبر یا، در سناریوهای پیچیدهتر، باز کردن بردارهای حمله برای دستکاری امضا شود.
TypeScript برای نجات: پیادهسازی ایمنی نوع احراز هویت
TypeScript ابزارهایی را برای حذف این دستههای کامل از اشکالات قبل از اجرای کد ارائه میدهد. با ایجاد یک قرارداد قوی برای ساختارهای داده و توابع خود، تشخیص خطا را از زمان اجرا به زمان کامپایل تغییر میدهیم.
مرحله 1: تعریف انواع رمزنگاری اصلی
اولین قدم ما این است که ابتداییترین رمزنگاری خود را با انواع صریح مدلسازی کنیم. به جای انتقال `string`های عمومی یا `any`ها، رابطها یا نام مستعار نوع دقیقی را تعریف میکنیم.
یک تکنیک قدرتمند در اینجا استفاده از انواع با نام تجاری (یا تایپ اسمی) است. این به ما امکان میدهد انواع متمایزی را ایجاد کنیم که از نظر ساختاری با `string` یکسان هستند اما قابل تعویض نیستند، که برای کلیدها و امضاها عالی است.
// types.ts
export type Brand
// Keys should not be treated as generic strings
export type PrivateKey = Brand
// The signature is also a specific type of string (e.g., base64)
export type Signature = Brand
// Define a set of allowed algorithms to prevent typos and misuse
export enum SignatureAlgorithm {
RS256 = 'RSA-SHA256',
ES256 = 'ECDSA-SHA256',
// Add other supported algorithms here
}
// Define a base interface for any data we want to sign
export interface Signable {
// We can enforce that any signable payload must be serializable
// For simplicity, we'll allow any object here, but in production
// you might enforce a structure like { [key: string]: string | number | boolean; }
[key: string]: any;
}
با این انواع، کامپایلر اکنون در صورت تلاش برای استفاده از یک `PublicKey` در جایی که انتظار یک `PrivateKey` میرود، یک خطا ایجاد میکند. شما نمیتوانید فقط هر رشته تصادفی را منتقل کنید؛ باید به صراحت به نوع دارای نام تجاری تبدیل شود، که قصد واضح را نشان میدهد.
مرحله 2: ساختن توابع امضا و تأیید نوع-ایمن
حالا، بیایید توابع خود را با استفاده از این انواع قوی بازنویسی کنیم. ما از ماژول `crypto` داخلی Node.js برای این مثال استفاده خواهیم کرد.
// crypto.service.ts
import * as crypto from 'crypto';
import { PrivateKey, PublicKey, Signature, SignatureAlgorithm, Signable } from './types';
export class DigitalSignatureService {
public sign
payload: T,
privateKey: PrivateKey,
algorithm: SignatureAlgorithm
): Signature {
// For consistency, we always stringify the payload in a deterministic way.
// Sorting keys ensures that {a:1, b:2} and {b:2, a:1} produce the same hash.
const stringifiedPayload = JSON.stringify(payload, Object.keys(payload).sort());
const signer = crypto.createSign(algorithm);
signer.update(stringifiedPayload);
signer.end();
const signature = signer.sign(privateKey, 'base64');
return signature as Signature;
}
public verify
payload: T,
signature: Signature,
publicKey: PublicKey,
algorithm: SignatureAlgorithm
): boolean {
const stringifiedPayload = JSON.stringify(payload, Object.keys(payload).sort());
const verifier = crypto.createVerify(algorithm);
verifier.update(stringifiedPayload);
verifier.end();
return verifier.verify(publicKey, signature, 'base64');
}
}
به تفاوت در امضاهای تابع نگاه کنید:
- `sign(payload: T, privateKey: PrivateKey, ...)`: اکنون به طور تصادفی انتقال یک کلید عمومی یا یک رشته عمومی به عنوان `privateKey` غیرممکن است. محموله توسط رابط `Signable` محدود شده است و ما از جنریکها (`
`) برای حفظ نوع خاص محموله استفاده میکنیم. - `verify(..., signature: Signature, publicKey: PublicKey, ...)`: آرگومانها به وضوح تعریف شدهاند. شما نمیتوانید امضا و کلید عمومی را با هم ترکیب کنید.
- `algorithm: SignatureAlgorithm`: با استفاده از enum، ما از اشتباهات املایی (`'RSA-SHA256'` در مقابل `'RSA-sha256'`) جلوگیری میکنیم و توسعهدهندگان را به یک لیست از پیش تأیید شده از الگوریتمهای امن محدود میکنیم و از حملات تنزل رمزنگاری در زمان کامپایل جلوگیری میکنیم.
مرحله 3: یک مثال عملی با نشانههای وب JSON (JWT)
امضاهای دیجیتال اساس نشانههای وب JSON (JWS) هستند که معمولاً برای ایجاد نشانههای وب JSON (JWT) استفاده میشوند. بیایید الگوهای نوع-ایمن خود را برای این مکانیسم احراز هویت همهجا حاضر اعمال کنیم.
ابتدا، ما یک نوع دقیق برای محموله JWT خود تعریف میکنیم. به جای یک شی عمومی، هر ادعای مورد انتظار و نوع آن را مشخص میکنیم.
// types.ts (extended)
export interface UserTokenPayload extends Signable {
iss: string; // Issuer
sub: string; // Subject (e.g., user ID)
aud: string; // Audience
exp: number; // Expiration time (Unix timestamp)
iat: number; // Issued at (Unix timestamp)
jti: string; // JWT ID
roles: string[]; // Custom claim
}
اکنون، سرویس تولید و اعتبارسنجی توکن ما میتواند به شدت بر اساس این محموله خاص تایپ شود.
// auth.service.ts
import { DigitalSignatureService } from './crypto.service';
import { PrivateKey, PublicKey, SignatureAlgorithm, UserTokenPayload } from './types';
class AuthService {
private signatureService = new DigitalSignatureService();
private privateKey: PrivateKey; // Loaded securely
private publicKey: PublicKey; // Publicly available
constructor(pk: PrivateKey, pub: PublicKey) {
this.privateKey = pk;
this.publicKey = pub;
}
// The function is now specific to creating user tokens
public generateUserToken(userId: string, roles: string[]): string {
const now = Math.floor(Date.now() / 1000);
const payload: UserTokenPayload = {
iss: 'https://api.my-global-app.com',
aud: 'my-global-app-clients',
sub: userId,
roles: roles,
iat: now,
exp: now + (60 * 15), // 15 minutes validity
jti: crypto.randomBytes(16).toString('hex'),
};
// The JWS standard uses base64url encoding, not just base64
const header = { alg: 'RS256', typ: 'JWT' }; // Algorithm must match key type
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
// Our type system doesn't understand JWS structure, so we need to construct it.
// A real implementation would use a library, but let's show the principle.
// Note: The signature must be on the 'encodedHeader.encodedPayload' string.
// For simplicity, we'll sign the payload object directly using our service.
const signature = this.signatureService.sign(
payload,
this.privateKey,
SignatureAlgorithm.RS256
);
// A proper JWT library would handle the base64url conversion of the signature.
// This is a simplified example to show type safety on the payload.
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
public validateAndDecodeToken(token: string): UserTokenPayload | null {
// In a real app, you would use a library like 'jose' or 'jsonwebtoken'
// which would handle parsing and verification.
const [header, payload, signature] = token.split('.');
if (!header || !payload || !signature) {
return null; // Invalid format
}
try {
const decodedPayload: unknown = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
// Now we use a type guard to validate the decoded object
if (!this.isUserTokenPayload(decodedPayload)) {
console.error('Decoded payload does not match expected structure.');
return null;
}
// Now we can safely use decodedPayload as UserTokenPayload
const isValid = this.signatureService.verify(
decodedPayload,
signature as Signature, // We need to cast here from string
this.publicKey,
SignatureAlgorithm.RS256
);
if (!isValid) {
console.error('Signature verification failed.');
return null;
}
if (decodedPayload.exp * 1000 < Date.now()) {
console.error('Token has expired.');
return null;
}
return decodedPayload;
} catch (error) {
console.error('Error during token validation:', error);
return null;
}
}
// This is a crucial Type Guard function
private isUserTokenPayload(payload: unknown): payload is UserTokenPayload {
if (typeof payload !== 'object' || payload === null) return false;
const p = payload as { [key: string]: unknown };
return (
typeof p.iss === 'string' &&
typeof p.sub === 'string' &&
typeof p.aud === 'string' &&
typeof p.exp === 'number' &&
typeof p.iat === 'number' &&
typeof p.jti === 'string' &&
Array.isArray(p.roles) &&
p.roles.every(r => typeof r === 'string')
);
}
}
حفاظت نوع `isUserTokenPayload` پل بین دنیای خارج از نوع و غیرقابل اعتماد (رشته توکن ورودی) و سیستم داخلی ایمن و تایپ شده ما است. پس از بازگشت این تابع `true`، TypeScript میداند که متغیر `decodedPayload` با رابط `UserTokenPayload` مطابقت دارد و به دسترسی ایمن به ویژگیهایی مانند `decodedPayload.sub` و `decodedPayload.exp` بدون هیچگونه `any` cast یا ترس از خطاهای `undefined` اجازه میدهد.
الگوهای معماری برای احراز هویت نوع-ایمن مقیاسپذیر
اعمال ایمنی نوع فقط به توابع جداگانه مربوط نمیشود. این مربوط به ساختن یک سیستم کامل است که در آن قراردادهای امنیتی توسط کامپایلر اعمال میشود. در اینجا چند الگوی معماری وجود دارد که این مزایا را گسترش میدهند.
مخزن کلید نوع-ایمن
در بسیاری از سیستمها، کلیدهای رمزنگاری توسط سرویس مدیریت کلید (KMS) مدیریت یا در یک گاوصندوق امن ذخیره میشوند. هنگامی که یک کلید را واکشی میکنید، باید اطمینان حاصل کنید که با نوع صحیح برگردانده میشود.
به جای تابعی مانند `getKey(keyId: string): Promise
// key.repository.ts
import { PublicKey, PrivateKey } from './types';
interface KeyRepository {
getPublicKey(keyId: string): Promise
// Example implementation (e.g., fetching from AWS KMS or Azure Key Vault)
class KmsRepository implements KeyRepository {
public async getPublicKey(keyId: string): Promise
public async getPrivateKey(keyId: string): Promise
با انتزاع واکشی کلید در پشت این رابط، بقیه برنامه شما نیازی به نگرانی در مورد ماهیت رشته-تایپ شده APIهای KMS ندارد. این میتواند به دریافت یک `PublicKey` یا `PrivateKey` تکیه کند و اطمینان حاصل کند که ایمنی نوع در سراسر پشته احراز هویت شما جریان دارد.
توابع ادعا برای اعتبار سنجی ورودی
حفاظت نوع عالی هستند، اما گاهی اوقات میخواهید بلافاصله در صورت شکست اعتبارسنجی، یک خطا پرتاب کنید. کلمه کلیدی `asserts` TypeScript برای این کار عالی است.
// A modification of our type guard
function assertIsUserTokenPayload(payload: unknown): asserts payload is UserTokenPayload {
if (!isUserTokenPayload(payload)) {
throw new Error('Invalid token payload structure.');
}
}
حالا، در منطق اعتبارسنجی خود، میتوانید این کار را انجام دهید:
const decodedPayload: unknown = JSON.parse(...); assertIsUserTokenPayload(decodedPayload); // From this point on, TypeScript KNOWS decodedPayload is of type UserTokenPayload console.log(decodedPayload.sub); // This is now 100% type-safe
این الگو کد اعتبارسنجی تمیزتر و خواناتر با جدا کردن منطق اعتبارسنجی از منطق کسب و کار که به دنبال آن میآید، ایجاد میکند.
پیامدهای جهانی و عامل انسانی
ساخت سیستمهای امن یک چالش جهانی است که شامل بیش از فقط کد میشود. این شامل افراد، فرآیندها و همکاری در سراسر مرزها و مناطق زمانی است. ایمنی نوع احراز هویت مزایای قابل توجهی را در این زمینه جهانی ارائه میدهد.
- به عنوان مستندات زنده عمل میکند: برای یک تیم توزیعشده، یک کدبیس با نوع خوب، نوعی مستندات دقیق و واضح است. یک توسعهدهنده جدید در یک کشور دیگر میتواند ساختارهای داده و قراردادهای سیستم احراز هویت را فقط با خواندن تعاریف نوع، فوراً درک کند. این سوءتفاهمها را کاهش میدهد و روند ادغام را سرعت میبخشد.
- ممیزیهای امنیتی را ساده میکند: هنگامی که ممیزان امنیتی کد شما را بررسی میکنند، یک پیادهسازی نوع-ایمن، هدف سیستم را بسیار واضح میکند. تأیید اینکه کلیدهای صحیح برای عملیات صحیح استفاده میشوند و ساختارهای داده به طور مداوم مدیریت میشوند، آسانتر است. این میتواند برای دستیابی به انطباق با استانداردهای بینالمللی مانند SOC 2 یا GDPR بسیار مهم باشد.
- قابلیت همکاری را افزایش میدهد: در حالی که TypeScript تضمینهای زمان کامپایل را ارائه میدهد، قالب روی-سیم داده را تغییر نمیدهد. یک JWT که توسط یک باطن TypeScript نوع-ایمن تولید میشود، هنوز یک JWT استاندارد است که میتواند توسط یک کلاینت موبایل که با Swift نوشته شده است یا یک سرویس شریک که با Go نوشته شده است، مصرف شود. ایمنی نوع یک گاردریل در زمان توسعه است که تضمین میکند شما در حال پیادهسازی صحیح استاندارد جهانی هستید.
- بار شناختی را کاهش میدهد: رمزنگاری سخت است. توسعهدهندگان نباید تمام جریان داده و قوانین نوع سیستم را در ذهن خود نگه دارند. با واگذاری این مسئولیت به کامپایلر TypeScript، توسعهدهندگان میتوانند بر روی منطق امنیتی سطح بالاتر تمرکز کنند، مانند اطمینان از بررسیهای انقضای صحیح و مدیریت خطای قوی، به جای نگرانی در مورد `TypeError: cannot read property 'sign' of undefined`.
نتیجهگیری: ایجاد اعتماد با انواع
امضاهای دیجیتال سنگ بنای امنیت دیجیتال مدرن هستند، اما پیادهسازی آنها در زبانهای تایپشده پویا مانند جاوااسکریپت یک فرآیند ظریف است که در آن کوچکترین خطا میتواند عواقب شدیدی داشته باشد. با پذیرش TypeScript، ما فقط در حال اضافه کردن انواع نیستیم. ما اساساً در حال تغییر رویکرد خود برای نوشتن کد امن هستیم.
ایمنی نوع احراز هویت، که از طریق انواع صریح، ابتداییترین نامگذاری شده، حفاظت نوع و معماری متفکرانه به دست میآید، یک شبکه ایمنی زمان کامپایل قدرتمند را فراهم میکند. این به ما امکان میدهد سیستمهایی بسازیم که نه تنها قویتر و کمتر مستعد آسیبپذیریهای رایج هستند، بلکه برای تیمهای جهانی نیز قابل درکتر، قابل نگهداریتر و قابل حسابرسیتر هستند.
در پایان، نوشتن کد امن در مورد مدیریت پیچیدگی و به حداقل رساندن عدم اطمینان است. TypeScript مجموعهای قدرتمند از ابزارها را در اختیار ما قرار میدهد تا دقیقاً همین کار را انجام دهیم و به ما امکان میدهد اعتماد دیجیتالی را که دنیای به هم پیوسته ما به آن وابسته است، ایجاد کنیم، یک تابع نوع-ایمن در یک زمان.