Scrypt
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
Scrypt는 암호화 키 파생 함수로, 메모리 집약적이고 시간 소모적인 계산을 통해 무차별 대입 공격에 대한 저항성을 높이도록 설계되었다. Scrypt는 Passphrase, Salt, CostFactor(N), BlockSizeFactor(r), ParallelizationFactor(p), DesiredKeyLen(dkLen) 등의 매개변수를 사용하며, PBKDF2와 ROMix, BlockMix, Salsa20/8 함수를 포함한 여러 단계를 거쳐 작동한다. Scrypt는 시간-메모리 트레이드오프를 통해 공격 비용을 증가시키도록 설계되었으며, 라이트코인, 도지코인과 같은 암호화폐의 작업 증명 알고리즘으로 사용된다. Scrypt 기반 암호화폐 채굴에는 GPU가 주로 사용되었으나, ASIC 채굴 하드웨어도 개발되었다. scrypt 유틸리티는 Scrypt 키 유도 함수의 데모를 위해 개발되었으며, 리눅스 및 BSD 배포판에서 사용할 수 있다.
더 읽어볼만한 페이지
- 암호 알고리즘 - 이진 코드
이진 코드는 0과 1을 사용하여 정보를 표현하는 시스템으로, 고대 중국의 주역에서 기원하며, 컴퓨터 과학, 통신 등 다양한 분야에서 활용된다. - 암호 알고리즘 - 메시지 인증 코드
메시지 인증 코드(MAC)는 메시지 무결성을 보장하기 위해 사용되는 암호화 기법으로, 송신자와 수신자가 동일한 키를 공유하여 MAC 생성, 확인 과정을 거친다. - 암호화 해시 함수 - RIPEMD
RIPEMD는 MD4를 기반으로 1992년 설계된 암호화 해시 함수로, 보안 취약점 보완을 위해 RIPEMD-128, RIPEMD-160, RIPEMD-256, RIPEMD-320 등의 변형이 개발되었으며, 특히 RIPEMD-160은 160비트 해시 값을 생성하고 다양한 라이브러리에서 지원되지만 보안성 우려가 제기되고 있다. - 암호화 해시 함수 - MD5
MD5는 로널드 리베스트 교수가 개발한 128비트 해시 값 생성 암호화 해시 함수이나, 보안 취약점으로 인해 현재는 보안이 중요한 분야에서는 사용이 중단되었다.
2. 역사
비밀번호 기반 키 파생 함수(KDF)는 일반적으로 계산 집약적으로 설계되어 계산에 비교적 긴 시간(수백 밀리초 정도)이 소요된다. 정상적인 사용자는 인증과 같은 작업을 할 때 함수를 한 번만 실행하므로 소요 시간은 무시할 수 있다. 그러나 무차별 대입 공격은 수십억 번의 연산을 필요로 할 수 있으며, 이 경우 긴 계산 시간이 공격을 어렵게 만든다.
스크립트 알고리즘은 입력된 비밀번호('Passphrase')와 솔트('Salt')를 사용하여 암호학적으로 안전한 파생 키('DerivedKey')를 생성하는 키 유도 함수이다. 이 알고리즘의 핵심적인 특징은 의도적으로 많은 양의 메모리를 사용하도록 설계되어, ASIC과 같은 특수 목적 하드웨어를 이용한 고속 병렬 공격을 어렵게 만든다는 점이다. 이는 시간-메모리 트레이드오프를 이용하는데, 알고리즘 실행 속도를 높이려면 많은 메모리가 필요하고, 메모리 사용량을 줄이려면 실행 시간이 매우 길어지도록 설계되었다.
RSA 시큐리티의 PBKDF2와 같은 기존의 비밀번호 기반 KDF는 상대적으로 적은 자원(복잡한 하드웨어나 많은 메모리 불필요)으로 구현 가능했다. 이 때문에 ASIC이나 FPGA 같은 하드웨어로 쉽고 저렴하게 구현될 수 있었다. 충분한 자원을 가진 공격자는 이러한 하드웨어를 수백, 수천 개 병렬로 구성하여 키 공간을 분할 탐색하는 대규모 병렬 공격을 감행할 수 있었고, 이는 공격 완료 시간을 크게 단축시킬 수 있었다.
scrypt 함수는 이러한 공격 시도를 방해하기 위해 알고리즘의 자원 요구량을 높이도록 설계되었다. 특히, 다른 비밀번호 기반 KDF에 비해 훨씬 많은 양의 메모리를 사용하도록 설계되어[6][20] 하드웨어 구현의 크기와 비용을 크게 증가시킨다. 이는 공격자가 제한된 비용으로 확보할 수 있는 병렬 처리 능력을 제한하는 효과를 가진다.
Scrypt는 많은 암호화폐에서 작업 증명 알고리즘(더 정확히는 해시캐시 작업 증명 알고리즘의 해시 함수)으로 사용된다. 2011년 9월에 출시된 암호화폐 테네브릭스(Tenebrix)에 처음 구현되었으며, 이는 자체 scrypt 알고리즘을 채택한 라이트코인과 도지코인의 기반이 되었다.[7][8][21][22] Scrypt를 사용하는 암호화폐 채굴은 CPU보다 처리 능력이 훨씬 높은 경향이 있는 GPU(그래픽 처리 장치)에서 주로 수행된다.[9][23] 이로 인해 2013년 11월과 12월에 해당 암호화폐들의 가격이 상승하면서 고성능 GPU의 부족 현상이 발생하기도 했다.[10][24]
2014년 5월 시점에는 scrypt 기반 암호화폐 채굴을 위한 전용 ASIC 하드웨어도 사용 가능하게 되었다.
3. 알고리즘
스크립트 알고리즘은 다음과 같은 주요 매개변수를 사용하여 동작을 조절한다.
스크립트 알고리즘은 크게 두 단계로 구성된다.
'''1단계: 비용이 많이 드는 솔트 생성'''
# 입력된 'Passphrase'와 'Salt'를 사용하여 PBKDF2HMAC-SHA256 함수를 실행하여 초기 데이터 블록 배열 `[B0...Bp−1]`을 생성한다. 이때 PBKDF2의 반복 횟수는 1로 고정되며, 생성되는 총 데이터 크기는 'blockSize' * p (즉, `128 * r * p`) 바이트이다.
# 생성된 각 초기 데이터 블록 `Bi` (총 `p`개)에 대해 ROMix 함수를 `N`('CostFactor')번 반복 적용한다. 이 과정은 `p`개의 스레드를 사용하여 병렬로 수행될 수 있다. ROMix 함수는 스크립트의 핵심으로, 많은 메모리를 사용하고 계산 비용이 높도록 설계되었다. ROMix 함수는 내부적으로 BlockMix 함수와 'Integerify' 연산을 사용하여 데이터를 복잡하게 혼합하며, 이 과정에서 큰 메모리 벡터('V')를 생성하고 접근하여 시간-메모리 트레이드오프를 유발한다. BlockMix 함수는 Salsa20 암호화 알고리즘의 8라운드 축소 버전인 Salsa20/8 해시 함수를 사용한다.
# ROMix 함수 처리가 완료된 `p`개의 블록들을 순서대로 모두 이어붙여 최종적인 "비용이 많이 드는 솔트"('expensiveSalt')를 생성한다.
'''2단계: 최종 키 생성'''
# 1단계에서 생성된 'expensiveSalt'를 새로운 솔트로 사용하고, 다시 원래의 'Passphrase'와 함께 PBKDF2HMAC-SHA256 함수를 실행한다. 이 단계에서도 PBKDF2의 반복 횟수는 1로 고정된다.
# 이 함수의 결과로 최종적인 파생 키('DerivedKey')가 'dkLen' 길이만큼 생성되어 반환된다.
스크립트가 많은 메모리를 요구하는 근본적인 이유는 1단계의 ROMix 함수 실행 중에 생성되고 반복적으로 접근되는 큰 의사 난수 데이터 벡터 때문이다. 이 벡터 전체를 RAM에 저장하면 ROMix 함수를 빠르게 실행할 수 있지만 메모리 사용량이 매우 커진다. 반대로 벡터 요소를 필요할 때마다 재계산하면 메모리 사용량은 줄일 수 있지만, 각 요소 계산 비용이 높기 때문에 전체 실행 시간이 매우 길어진다. 스크립트는 이러한 시간-메모리 트레이드오프를 양쪽 방향 모두 비용이 많이 들도록 의도적으로 설계하여, 공격자가 제한된 자원으로 효율적인 공격을 수행하기 어렵게 만든다.
3. 1. ROMix 함수
ROMix 함수는 스크립트(scrypt) 알고리즘의 핵심적인 부분으로, 많은 메모리를 사용하도록 설계되어 ASIC과 같은 특수 하드웨어 제작을 통한 암호 해독 공격을 어렵게 만든다. 이 함수는 입력된 데이터 블록을 여러 단계에 걸쳐 복잡하게 섞는 역할을 수행한다.
스크립트 알고리즘의 1단계(비용이 많이 드는 솔트 생성)에서 PBKDF2를 통해 생성된 초기 데이터 블록 배열 `[B0...Bp−1]`의 각 블록 `Bi`는 ROMix 함수를 사용하여 `CostFactor`(N) 횟수만큼 반복적으로 처리된다. 이 과정은 병렬로 수행될 수 있다.
Bi ← ROMix(Bi, CostFactor)
=== ROMix 함수 정의 ===
ROMix 함수는 입력으로 데이터 블록(`Block`)과 반복 횟수(`Iterations`)를 받는다. 스크립트 알고리즘에서는 `Iterations` 값으로 `CostFactor`(N)이 사용된다.
'''함수''' ROMix(Block, Iterations)
# 입력된 `Block`을 `X` 변수에 할당한다.
# `V`라는 배열을 생성한다.
# i를 0부터 `Iterations` - 1까지 반복하면서 다음을 수행한다:
## 현재 `X`의 값을 `Vi`에 저장한다 (`Vi ← X`).
## `X`를 `BlockMix` 함수를 이용해 갱신한다 (`X ← BlockMix(X)`).
# 다시 i를 0부터 `Iterations` - 1까지 반복하면서 다음을 수행한다:
## `X`의 마지막 64바이트를 리틀 엔디안 정수로 해석하고(`Integerify(X)`), 이 값을 `Iterations`로 나눈 나머지를 인덱스 `j`로 사용한다 (`j ← Integerify(X) mod Iterations`).
## 현재 `X`와 이전에 저장된 `Vj`를 XOR 연산한 결과를 `BlockMix` 함수를 이용해 처리하여 `X`를 갱신한다 (`X ← BlockMix(X xor Vj)`).
# 최종적으로 계산된 `X` 값을 반환한다.
여기서 `Integerify(X)` 함수는 RFC 7914에 정의되어 있으며, 입력 블록 `X`의 마지막 64바이트를 리틀 엔디안 방식의 정수로 해석하는 것을 의미한다. `Iterations` 값이 2의 거듭제곱(N)이므로, 실제로는 `Integerify(X) mod Iterations` 계산 시 마지막 64바이트 중 처음 `Ceiling(N / 8)` 바이트만 필요하다.
=== BlockMix 함수 정의 ===
BlockMix 함수는 ROMix 함수 내부에서 사용되며, 입력된 블록 `B`를 더 작은 단위로 나누어 Salsa20 해시 함수의 변형을 이용해 섞는 역할을 한다.
'''함수''' BlockMix(B)
# 입력 블록 `B`의 길이를 128로 나누어 블록 크기 `r`을 계산한다 (`r ← Length(B) / 128`). 이는 블록 `B`가 `r`개의 128바이트 청크, 또는 `2r`개의 64바이트 청크로 구성됨을 의미한다.
# 블록 `B`를 `2r`개의 64바이트 청크 배열 `[B0...B2r-1]`로 취급한다.
# 마지막 청크 `B2r−1`을 `X` 변수에 할당한다.
# `Y`라는 배열을 생성한다.
# i를 0부터 `2r` - 1까지 반복하면서 다음을 수행한다:
## 현재 `X`와 `Bi`를 XOR 연산한 결과를 Salsa20의 8라운드 버전인 `Salsa20/8` 함수로 해시하여 `X`를 갱신한다 (`X ← Salsa20/8(X xor Bi)`). `Salsa20/8`은 64바이트 입력을 받아 64바이트 출력을 생성한다.
## 갱신된 `X` 값을 `Yi`에 저장한다 (`Yi ← X`).
# 배열 `Y`의 짝수 인덱스 요소(`Y0, Y2, ..., Y2r-2`)들과 홀수 인덱스 요소(`Y1, Y3, ..., Y2r-1`)들을 순서대로 이어붙여서 반환한다 (`Y0∥Y2∥...∥Y2r−2 ∥ Y1∥Y3∥...∥Y2r−1`).
3. 2. BlockMix 함수
BlockMix 함수는 scrypt 알고리즘 내의 ROMix 함수에서 사용되는 핵심적인 혼합 함수이다. 입력된 블록 데이터를 더 예측하기 어렵게 만드는 역할을 수행한다.
'''함수 정의 및 동작 과정'''
BlockMix 함수는 입력으로 블록 데이터 B를 받는다. 이 블록 B는 r개의 128바이트 청크(chunk), 즉 2r개의 64바이트 청크로 구성된다. 함수의 동작은 다음과 같다.
# 입력 블록 B의 전체 길이를 128로 나누어 정수 r 값을 계산한다. (r ← Length(B) / 128)
# 입력 블록 B를 2r개의 64바이트 청크 배열 [B0, B1, ..., B2r-1]로 취급한다.
# 배열의 마지막 청크인 B2r−1를 초기값으로 변수 X에 할당한다. (X ← B2r−1)
# i를 0부터 2r-1까지 1씩 증가시키며 다음 과정을 반복한다:
## 현재 변수 X와 i번째 청크 Bi를 XOR 연산한다.
## XOR 연산의 결과를 Salsa20 해시 함수의 8 라운드 축소 버전인 Salsa20/8 함수에 입력한다. 이 함수의 출력(새로운 64바이트 값)을 다시 변수 X에 할당한다. (X ← Salsa20/8(X xor Bi))
## 이 단계에서 계산된 X 값을 임시 배열 Y의 i번째 요소 Yi에 저장한다. (Yi ← X)
# 모든 반복(i = 0부터 2r-1까지)이 완료된 후, 배열 Y에 저장된 값들을 특정 순서로 조합하여 최종 결과를 반환한다. 먼저 짝수 인덱스에 해당하는 값들(Y0, Y2, ..., Y2r-2)을 순서대로 이어붙이고, 그 뒤에 홀수 인덱스에 해당하는 값들(Y1, Y3, ..., Y2r-1)을 순서대로 이어붙여 최종 출력 블록을 생성한다. (return ← Y0∥Y2∥...∥Y2r−2 ∥ Y1∥Y3∥...∥Y2r−1)
여기서 Salsa20/8은 Salsa20 알고리즘의 라운드 수를 8로 줄인 버전을 의미한다.
3. 3. Salsa20/8 함수
Salsa20/8 함수는 Scrypt 알고리즘의 내부 구성 요소 중 하나인 ''BlockMix'' 함수 내에서 사용되는 해시 함수이다. 이 함수는 64바이트 크기의 입력을 받아 배타적 논리합(XOR) 연산을 거친 후, 다시 64바이트 크기의 출력값을 생성하는 역할을 한다.
''BlockMix'' 함수는 입력된 블록 `B`를 2r개의 64바이트 청크(B0부터 B2r-1까지)로 나눈다. 이후 마지막 청크(B2r-1)를 초기값 X로 설정하고, 각 청크 Bi와 이전 결과값 X를 XOR 연산한 값을 Salsa20/8 함수에 입력하여 새로운 X 값을 계산하는 과정을 반복한다. 이 계산 과정은 다음과 같이 표현될 수 있다.
: X ← B2r−1
: '''for''' i ← 0 '''to''' 2r−1 '''do'''
: : X ← Salsa20/8(X xor Bi)
: : Yi ← X
Salsa20/8 함수는 이름에서 알 수 있듯이, Salsa20 암호화 알고리즘을 기반으로 한다. 표준 Salsa20 알고리즘이 20 라운드를 수행하는 것과 달리, Salsa20/8은 8 라운드만 수행하는 축소된 버전이다.
4. 시간-메모리 트레이드오프
비밀번호 기반 키 파생 함수(KDF)는 일반적으로 계산하는 데 비교적 오랜 시간(예: 수백 밀리초 정도)이 걸리도록 계산 집약적으로 설계된다. 합법적인 사용자는 인증과 같은 작업을 할 때 함수를 한 번만 수행하면 되므로 이 시간은 무시할 수 있다. 그러나 무차별 대입 공격은 수십억 번의 작업을 수행해야 할 수 있으므로, 이때 필요한 시간은 공격을 어렵게 만드는 중요한 요소가 된다.
PBKDF2와 같이 이전에 널리 사용되던 비밀번호 기반 KDF는 상대적으로 적은 자원, 즉 복잡한 하드웨어나 많은 메모리를 요구하지 않았다. 이 때문에 ASIC이나 FPGA 같은 특수 하드웨어에서 비교적 쉽고 저렴하게 구현될 수 있었다. 이는 충분한 자원을 가진 공격자가 수백 또는 수천 개의 하드웨어 구현을 만들어 각각 다른 키 영역을 동시에 검색하는 대규모 병렬 공격을 가능하게 했다. 결과적으로 무차별 대입 공격에 필요한 전체 시간이 크게 단축될 수 있었다.
scrypt 함수는 이러한 공격 시도를 막기 위해 알고리즘의 자원 요구량을 의도적으로 높여 설계되었다. 특히, 다른 비밀번호 기반 KDF에 비해 훨씬 많은 양의 메모리를 사용하도록 만들어졌다.[6][20] 이는 하드웨어 구현의 크기와 비용을 크게 증가시켜, 공격자가 제한된 예산으로 동원할 수 있는 병렬 처리의 양을 제한하는 효과를 가진다.
scrypt가 대량의 메모리를 요구하는 이유는 알고리즘 실행 과정에서 생성되는 매우 큰 의사 난수 비트열 벡터 때문이다. 이 벡터가 생성된 후에는 벡터 내 요소들에 의사 난수적인 순서로 접근하여 최종적인 파생 키를 생성한다. 간단한 구현 방식에서는 이 전체 벡터를 RAM에 저장해두고 필요할 때마다 접근해야 한다.
물론 벡터의 각 요소는 알고리즘적으로 생성되므로, 필요할 때마다 실시간으로 계산하여 메모리에는 한 번에 하나의 요소만 저장하는 방식으로 메모리 요구량을 크게 줄일 수도 있다. 하지만 각 요소를 생성하는 과정 자체가 계산 비용이 많이 들도록 설계되었고, 함수 실행 중에 각 요소에 여러 번 접근해야 할 수도 있다. 따라서 메모리 사용량을 줄이려면 속도 면에서 상당한 손해를 감수해야 한다.
이러한 종류의 시간-메모리 트레이드오프는 많은 컴퓨터 알고리즘에 존재한다. 즉, 더 많은 메모리를 사용하여 속도를 높이거나, 반대로 더 많은 계산을 수행하고 시간을 더 소모하는 대신 메모리 사용량을 줄이는 선택이 가능하다. scrypt의 핵심 아이디어는 이 트레이드오프의 양쪽 방향 모두 비용이 많이 들도록 의도적으로 설계하는 것이다. 결과적으로 공격자는 (1) 적은 자원을 사용하지만 실행 속도가 매우 느린 구현을 사용하거나, (2) 실행 속도는 빠르지만 매우 큰 메모리를 요구하여 병렬화 비용이 비싼 구현을 사용하는 것 중 하나를 선택해야 하므로 공격 비용이 증가하게 된다.
5. 암호화폐에서의 사용
Scrypt는 많은 암호화폐에서 작업 증명 알고리즘(더 정확히는 해시캐시 작업 증명 알고리즘의 해시 함수)으로 사용된다.[7][8] Scrypt는 2011년 9월에 출시된 Tenebrix라는 암호화폐에 처음 구현되었으며, 이는 이후 scrypt 알고리즘을 채택한 라이트코인과 도지코인의 기반이 되었다.[7][8][21][22]
Scrypt를 사용하는 암호화폐의 채굴은 주로 그래픽 처리 장치(GPU)에서 이루어진다. 이는 중앙 처리 장치(CPU)에 비해 GPU가 특정 알고리즘에서 훨씬 더 높은 처리 능력을 가지는 경향이 있기 때문이다.[9][23] 이러한 이유로 2013년 11월과 12월에는 해당 암호화폐들의 가격이 상승하면서 고급 GPU가 부족해지는 현상이 발생하기도 했다.[10][24]
2014년 5월부터는 scrypt 기반 암호화폐 채굴을 위한 특수한 주문형 반도체(ASIC) 채굴 하드웨어도 사용 가능하게 되었다.
6. 유틸리티
scrypt 유틸리티는 2009년 5월 콜린 퍼시벌에 의해 scrypt 키 유도 함수의 시연용으로 작성되었다.[2][3][16][17] 이 유틸리티는 대부분의 리눅스와 BSD 배포판에서 사용할 수 있다.
참조
[1]
웹사이트
Colin Percival
https://twitter.com/[...]
[2]
웹사이트
The scrypt key derivation function
http://www.tarsnap.c[...]
2014-01-21
[3]
웹사이트
SCRYPT(1) General Commands Manual
https://manpages.deb[...]
2022-03-02
[4]
웹사이트
The scrypt Password-Based Key Derivation Function
https://datatracker.[...]
RFC Editor
2016-08
[5]
웹사이트
Beyond Bitcoin: A Guide to the Most Promising Cryptocurrencies
https://motherboard.[...]
2013-11-29
[6]
웹사이트
Stronger Key Derivation Via Sequential Memory-Hard Functions
http://www.tarsnap.c[...]
2022-11-11
[7]
서적
Mastering Bitcoin: Unlocking Digital Cryptocurrencies
https://books.google[...]
O'Reilly Media
2014-12-03
[8]
웹사이트
History of cryptocurrency
https://web.archive.[...]
2014-06-27
[9]
서적
Litecoin Scrypt Mining Configurations for Radeon 7950
https://www.amazon.c[...]
Amazon Digital Services
[10]
웹사이트
Massive surge in Litecoin mining leads to graphics card shortage
http://www.extremete[...]
ExtremeTech
2013-12-10
[11]
웹사이트
Bcrypt – Blowfish File Encryption (homepage)
http://bcrypt.source[...]
2024-04-08
[12]
웹사이트
bcrypt APK for Android – free download on Droid Informer
https://droidinforme[...]
2022-03-02
[13]
웹사이트
T2 package – trunk – bcrypt – A utility to encrypt files.
http://t2sde.org/pac[...]
2022-03-02
[14]
웹사이트
Oracle® GoldenGate Licensing Information
https://docs.oracle.[...]
2024-04-08
[15]
웹사이트
Colin Percival
https://twitter.com/[...]
2022-11-11
[16]
웹사이트
The scrypt key derivation function
http://www.tarsnap.c[...]
2014-01-21
[17]
웹사이트
SCRYPT(1) General Commands Manual
https://manpages.deb[...]
2022-03-02
[18]
웹사이트
The scrypt Password-Based Key Derivation Function
https://datatracker.[...]
RFC Editor
2021-12-13
[19]
웹사이트
Beyond Bitcoin: A Guide to the Most Promising Cryptocurrencies
https://motherboard.[...]
2022-11-11
[20]
웹사이트
Stronger Key Derivation Via Sequential Memory-Hard Functions
http://www.tarsnap.c[...]
2022-11-11
[21]
서적
Mastering Bitcoin: Unlocking Digital Cryptocurrencies
https://books.google[...]
O'Reilly Media
2014-12-03
[22]
웹사이트
History of cryptocurrency
https://web.archive.[...]
2014-06-27
[23]
서적
Litecoin Scrypt Mining Configurations for Radeon 7950
https://www.amazon.c[...]
Amazon Digital Services
[24]
웹사이트
Massive surge in Litecoin mining leads to graphics card shortage
http://www.extremete[...]
ExtremeTech
2022-11-11
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com