Security
GIWA SDK 사용 시 보안 모범 사례입니다.
Security Architecture
┌─────────────────────────────────────────┐
│ App Layer │
│ - Discard mnemonic immediately after │
│ displaying │
│ - Minimize private key time in memory │
│ - Never log sensitive data │
├─────────────────────────────────────────┤
│ GIWA SDK Layer │
│ - Input validation │
│ - Exclude sensitive info from errors │
├─────────────────────────────────────────┤
│ Native Secure Storage │
│ iOS: Keychain (Secure Enclave) │
│ Android: Keystore (Hardware-backed) │
├─────────────────────────────────────────┤
│ OS-Level Encryption │
└─────────────────────────────────────────┘
Private Key Management
Safe Wallet Creation
const handleCreateWallet = async () => {
try {
const { wallet, mnemonic } = await createWallet();
// Show mnemonic to user and confirm backup
const confirmed = await showMnemonicBackupScreen(mnemonic);
if (!confirmed) {
// Warn user if backup not confirmed
Alert.alert(
'Warning',
'You cannot recover your wallet without backing up your recovery phrase'
);
}
// Mnemonic is no longer accessible in the app after this
// SDK does not store the mnemonic
} catch (error) {
// Do not include sensitive information in error messages
Alert.alert('Error', 'Failed to create wallet');
}
};
Private Key Export Precautions
const handleExportPrivateKey = async () => {
// 1. Warn user about risks
const confirmed = await new Promise((resolve) => {
Alert.alert(
'Warning',
'Exposing your private key can result in loss of assets.\n\n' +
'Never:\n' +
'- Take screenshots of your private key\n' +
'- Share it with anyone\n' +
'- Store it in the cloud',
[
{ text: 'Cancel', onPress: () => resolve(false) },
{ text: 'I Understand', onPress: () => resolve(true) },
]
);
});
if (!confirmed) return;
try {
// 2. Biometric authentication (automatically requested)
const privateKey = await exportPrivateKey();
// 3. Auto-hide after a set time
showPrivateKeyModal(privateKey, {
autoHideAfter: 60000, // Auto-hide after 60 seconds
disableScreenshot: true, // Prevent screenshots (Android)
});
} catch (error) {
if (error.code === 'BIOMETRIC_FAILED') {
Alert.alert('Authentication Failed', 'Biometric authentication failed');
}
}
};
Biometric Authentication
import { useBiometricAuth } from 'giwa-react-native-wallet';
function SecureAction() {
const { authenticate, isAvailable, biometryType } = useBiometricAuth();
const handleSensitiveAction = async () => {
if (!isAvailable) {
// Use alternative authentication like PIN when biometrics unavailable
const pinValid = await verifyPin();
if (!pinValid) return;
} else {
// Biometric authentication
const success = await authenticate({
promptMessage: 'Authenticate to approve transaction',
});
if (!success) return;
}
// Execute sensitive action
await performSensitiveAction();
};
}
Transaction Security
Address Validation
import { GiwaError, ErrorCodes } from 'giwa-react-native-wallet';
const validateAndSend = async (to: string, amount: string) => {
// 1. Validate address format
if (!/^0x[a-fA-F0-9]{40}$/.test(to)) {
throw new GiwaError('Invalid address format', ErrorCodes.INVALID_ADDRESS);
}
// 2. Prevent sending to self
if (to.toLowerCase() === wallet.address.toLowerCase()) {
Alert.alert('Warning', 'Are you sure you want to send to yourself?');
}
// 3. Validate amount
const amountWei = parseEther(amount);
if (amountWei <= 0n) {
throw new GiwaError('Amount must be greater than 0');
}
// 4. Check balance
if (amountWei > balance) {
throw new GiwaError('Insufficient balance', ErrorCodes.INSUFFICIENT_FUNDS);
}
// 5. Additional confirmation for large amounts
const threshold = parseEther('1'); // 1 ETH
if (amountWei >= threshold) {
const confirmed = await confirmLargeTransaction(amount);
if (!confirmed) return;
}
await sendTransaction({ to, value: amount });
};
Transaction Simulation
const safeSendTransaction = async (tx) => {
// Simulate before sending
try {
await estimateGas(tx);
} catch (error) {
Alert.alert(
'Transaction Expected to Fail',
'This transaction is expected to fail. Do you want to continue?'
);
return;
}
await sendTransaction(tx);
};
Phishing Prevention
RPC URL Validation
// Automatically validated internally by SDK
const ALLOWED_RPC_DOMAINS = [
'giwa.io',
'sepolia-rpc.giwa.io',
'rpc.giwa.io',
];
// Warning displayed when using custom RPC
<GiwaProvider
config={{
network: 'mainnet',
customRpcUrl: 'https://custom-rpc.example.com', // Warning shown
}}
>
Contract Interaction Validation
// Verify known contract addresses
import { CONTRACT_ADDRESSES, getContractAddresses } from 'giwa-react-native-wallet';
const isOfficialContract = (address: string) => {
const contracts = getContractAddresses('mainnet');
return Object.values(contracts).includes(address.toLowerCase());
};
// Confirm before token approval
const safeApprove = async (tokenAddress: string, spender: string, amount: string) => {
if (!isOfficialContract(spender)) {
const confirmed = await Alert.alert(
'Unknown Contract',
`${spender} is not an official GIWA contract.\nDo you want to continue?`,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Continue', style: 'destructive' },
]
);
if (!confirmed) return;
}
await approve(tokenAddress, spender, amount);
};
Built-in SDK Security Features
Rate Limiting
민감한 작업에 대한 브루트포스 공격을 방지합니다.
// Mnemonic/private key export limits
const { exportMnemonic, exportPrivateKey } = useGiwaWallet();
// Limited to 3 calls per minute
// 5-minute cooldown when exceeded
try {
const mnemonic = await exportMnemonic();
} catch (error) {
if (error.code === 'RATE_LIMIT_EXCEEDED') {
// "Rate limit exceeded. Please wait 300 seconds."
Alert.alert('Please try again later', error.message);
}
}
| 작업 | 제한 | 쿨다운 |
|---|---|---|
exportMnemonic | 3회/분 | 5분 |
exportPrivateKey | 3회/분 | 5분 |