105 lines
3.8 KiB
Markdown
105 lines
3.8 KiB
Markdown
# SHiNE Arweave Wallet Derivation v1
|
||
|
||
Сокращение: **SAWD-v1**.
|
||
|
||
## Назначение
|
||
Из 32-байтного `deviceKey32` пользователя получить один и тот же нативный Arweave RSA-4096 JWK wallet и один и тот же Arweave address.
|
||
|
||
## Вход
|
||
- `deviceKey32`: ровно 32 байта.
|
||
- Если исходный `device.key` хранится как Ed25519 PKCS8 base64, нужно извлечь последние 32 байта из PKCS8.
|
||
- Если используется Solana keypair JSON на 64 байта, используются только `bytes[0..31]`.
|
||
|
||
## Выход
|
||
```json
|
||
{
|
||
"derivation": "SAWD-v1",
|
||
"jwk": {
|
||
"kty": "RSA",
|
||
"e": "AQAB",
|
||
"n": "...",
|
||
"d": "...",
|
||
"p": "...",
|
||
"q": "...",
|
||
"dp": "...",
|
||
"dq": "...",
|
||
"qi": "..."
|
||
},
|
||
"owner": "...",
|
||
"address": "..."
|
||
}
|
||
```
|
||
|
||
Где:
|
||
- `owner = jwk.n`
|
||
- `address = base64url_no_padding(SHA-256(unsigned_big_endian_bytes(n)))`
|
||
|
||
## Константы
|
||
- `DERIVATION_NAME = "SAWD-v1"`
|
||
- `MASTER_LABEL = "SHINE/ARWEAVE/RSA4096/SAWD-v1/MASTER"`
|
||
- `STREAM_LABEL = "SHINE/ARWEAVE/RSA4096/SAWD-v1/STREAM"`
|
||
- `MR_LABEL = "SHINE/ARWEAVE/RSA4096/SAWD-v1/MILLER-RABIN"`
|
||
- `RSA_BITS = 4096`
|
||
- `PRIME_BITS = 2048`
|
||
- `PUBLIC_EXPONENT = 65537`
|
||
- `MILLER_RABIN_ROUNDS = 64`
|
||
- `SMALL_PRIME_LIMIT = 10000`
|
||
|
||
## Алгоритм
|
||
1. Проверить `deviceKey32.length == 32`.
|
||
2. `masterSeed32 = HMAC-SHA256(key = UTF8(MASTER_LABEL), message = deviceKey32)`.
|
||
3. Реализовать `deriveBytes(label, length)`:
|
||
- `output = empty`
|
||
- `counter = 0`
|
||
- while `output.length < length`:
|
||
- `block = HMAC-SHA256(key = masterSeed32, message = UTF8(STREAM_LABEL) || UTF8("/") || UTF8(label) || UTF8("/") || uint64_be(counter))`
|
||
- `output = output || block`
|
||
- `counter++`
|
||
- вернуть первые `length` байт.
|
||
4. Для `p` и `q`:
|
||
- `raw = deriveBytes(label + "/" + index, 256)`
|
||
- `candidate = unsigned_big_endian_integer(raw)`
|
||
- `candidate = candidate OR 2^2047`
|
||
- `candidate = candidate OR 1`
|
||
- Проверить:
|
||
- `bitLength(candidate) == 2048`
|
||
- `candidate odd`
|
||
- не делится на малые простые `<= 10000`
|
||
- `gcd(candidate - 1, 65537) == 1`
|
||
- проходит Miller-Rabin `64 rounds`
|
||
5. Базы Miller-Rabin детерминированные:
|
||
- `baseBytes = HMAC-SHA256(key = masterSeed32, message = UTF8(MR_LABEL) || UTF8("/") || UTF8(label) || UTF8("/") || uint64_be(index) || UTF8("/") || uint32_be(round))`
|
||
- `a = 2 + (unsigned_big_endian_integer(baseBytes) mod (candidate - 3))`
|
||
6. `p = derivePrime("p")`, `q = derivePrime("q")`.
|
||
7. Если `p == q`, продолжить поиск `q`.
|
||
8. Если `p > q`, поменять местами. В SAWD-v1 всегда `p < q`.
|
||
9. `n = p * q`
|
||
10. `e = 65537`
|
||
11. `lambda = lcm(p - 1, q - 1)`
|
||
12. `d = modular_inverse(e, lambda)`
|
||
13. `dp = d mod (p - 1)`
|
||
14. `dq = d mod (q - 1)`
|
||
15. `qi = modular_inverse(q, p)`
|
||
16. Сформировать JWK:
|
||
- `kty = "RSA"`
|
||
- `e = "AQAB"`
|
||
- `n,d,p,q,dp,dq,qi = base64url unsigned big-endian integer without padding`
|
||
17. `owner = jwk.n`
|
||
18. `address = base64url_no_padding(SHA-256(unsigned_big_endian_bytes(n)))`
|
||
|
||
## Запрещено
|
||
- `crypto.generateKeyPair`
|
||
- `WebCrypto generateKey`
|
||
- `KeyPairGenerator`
|
||
- `SecureRandom(seed)`
|
||
- `Math.random`
|
||
- системный `random`
|
||
- ArDrive CLI
|
||
- Turbo
|
||
- внешний API для генерации ключа
|
||
- сохранение приватного JWK
|
||
|
||
## Версионирование стандарта
|
||
Если меняется любая константа или шаг алгоритма — это уже **SAWD-v2**.
|
||
Пользователи, созданные на SAWD-v1, должны продолжать восстанавливаться через SAWD-v1.
|