SHiNE-server/Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md

3.8 KiB
Raw Permalink Blame History

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].

Выход

{
  "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
  1. owner = jwk.n
  2. 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.