# SHiNE Arweave Wallet Derivation v1 Сокращение: **SAWD-v1**. ## Назначение Из 32-байтного `clientKey32` пользователя получить один и тот же нативный Arweave RSA-4096 JWK wallet и один и тот же Arweave address. ## Вход - `clientKey32`: ровно 32 байта. - Если исходный `client.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. Проверить `clientKey32.length == 32`. 2. `masterSeed32 = HMAC-SHA256(key = UTF8(MASTER_LABEL), message = clientKey32)`. 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.