3.8 KiB
3.8 KiB
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.naddress = 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 = 4096PRIME_BITS = 2048PUBLIC_EXPONENT = 65537MILLER_RABIN_ROUNDS = 64SMALL_PRIME_LIMIT = 10000
Алгоритм
- Проверить
deviceKey32.length == 32. masterSeed32 = HMAC-SHA256(key = UTF8(MASTER_LABEL), message = deviceKey32).- Реализовать
deriveBytes(label, length):output = emptycounter = 0- while
output.length < length:block = HMAC-SHA256(key = masterSeed32, message = UTF8(STREAM_LABEL) || UTF8("/") || UTF8(label) || UTF8("/") || uint64_be(counter))output = output || blockcounter++
- вернуть первые
lengthбайт.
- Для
pиq:raw = deriveBytes(label + "/" + index, 256)candidate = unsigned_big_endian_integer(raw)candidate = candidate OR 2^2047candidate = candidate OR 1- Проверить:
bitLength(candidate) == 2048candidate odd- не делится на малые простые
<= 10000 gcd(candidate - 1, 65537) == 1- проходит Miller-Rabin
64 rounds
- Базы 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))
p = derivePrime("p"),q = derivePrime("q").- Если
p == q, продолжить поискq. - Если
p > q, поменять местами. В SAWD-v1 всегдаp < q. n = p * qe = 65537lambda = lcm(p - 1, q - 1)d = modular_inverse(e, lambda)dp = d mod (p - 1)dq = d mod (q - 1)qi = modular_inverse(q, p)- Сформировать JWK:
kty = "RSA"e = "AQAB"n,d,p,q,dp,dq,qi = base64url unsigned big-endian integer without padding
owner = jwk.naddress = base64url_no_padding(SHA-256(unsigned_big_endian_bytes(n)))
Запрещено
crypto.generateKeyPairWebCrypto generateKeyKeyPairGeneratorSecureRandom(seed)Math.random- системный
random - ArDrive CLI
- Turbo
- внешний API для генерации ключа
- сохранение приватного JWK
Версионирование стандарта
Если меняется любая константа или шаг алгоритма — это уже SAWD-v2.
Пользователи, созданные на SAWD-v1, должны продолжать восстанавливаться через SAWD-v1.