skeleton_key 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +542 -0
- data/bin/console +8 -0
- data/bin/lint +10 -0
- data/bin/setup +21 -0
- data/lib/skeleton_key/chains/bitcoin/account.rb +101 -0
- data/lib/skeleton_key/chains/bitcoin/account_derivation.rb +127 -0
- data/lib/skeleton_key/chains/bitcoin/support/outputs.rb +77 -0
- data/lib/skeleton_key/chains/bitcoin/support/paths.rb +34 -0
- data/lib/skeleton_key/chains/bitcoin/support/versioning.rb +87 -0
- data/lib/skeleton_key/chains/bitcoin/support.rb +48 -0
- data/lib/skeleton_key/chains/ethereum/account.rb +191 -0
- data/lib/skeleton_key/chains/ethereum/support.rb +143 -0
- data/lib/skeleton_key/chains/solana/account.rb +117 -0
- data/lib/skeleton_key/chains/solana/support.rb +27 -0
- data/lib/skeleton_key/codecs/base58.rb +64 -0
- data/lib/skeleton_key/codecs/base58_check.rb +42 -0
- data/lib/skeleton_key/codecs/bech32.rb +182 -0
- data/lib/skeleton_key/constants.rb +68 -0
- data/lib/skeleton_key/core/entropy.rb +37 -0
- data/lib/skeleton_key/derivation/bip32.rb +182 -0
- data/lib/skeleton_key/derivation/path.rb +112 -0
- data/lib/skeleton_key/derivation/slip10.rb +89 -0
- data/lib/skeleton_key/errors.rb +158 -0
- data/lib/skeleton_key/keyring.rb +63 -0
- data/lib/skeleton_key/recovery/bip39.rb +212 -0
- data/lib/skeleton_key/recovery/bip39_english.txt +2048 -0
- data/lib/skeleton_key/recovery/slip39.rb +220 -0
- data/lib/skeleton_key/recovery/slip39_support/bit_packing.rb +37 -0
- data/lib/skeleton_key/recovery/slip39_support/checksum.rb +53 -0
- data/lib/skeleton_key/recovery/slip39_support/cipher.rb +81 -0
- data/lib/skeleton_key/recovery/slip39_support/decoder.rb +109 -0
- data/lib/skeleton_key/recovery/slip39_support/encoder.rb +48 -0
- data/lib/skeleton_key/recovery/slip39_support/generated_set.rb +39 -0
- data/lib/skeleton_key/recovery/slip39_support/generator.rb +156 -0
- data/lib/skeleton_key/recovery/slip39_support/interpolation.rb +71 -0
- data/lib/skeleton_key/recovery/slip39_support/protocol.rb +34 -0
- data/lib/skeleton_key/recovery/slip39_support/secret_recovery.rb +74 -0
- data/lib/skeleton_key/recovery/slip39_support/share.rb +50 -0
- data/lib/skeleton_key/recovery/slip39_wordlist.txt +1024 -0
- data/lib/skeleton_key/seed.rb +127 -0
- data/lib/skeleton_key/skeleton_key.code-workspace +11 -0
- data/lib/skeleton_key/utils/encoding.rb +134 -0
- data/lib/skeleton_key/utils/hashing.rb +238 -0
- data/lib/skeleton_key/version.rb +8 -0
- data/lib/skeleton_key.rb +66 -0
- metadata +107 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module SkeletonKey
|
|
4
|
+
##
|
|
5
|
+
# Canonical seed container shared by all chain derivation entry points.
|
|
6
|
+
#
|
|
7
|
+
# {Seed} is the normalization boundary between recovery inputs
|
|
8
|
+
# (BIP39/SLIP-0039), raw entropy-like byte material, and downstream chain
|
|
9
|
+
# derivation. Callers can import from several supported input forms, but once
|
|
10
|
+
# constructed the object always wraps validated raw bytes.
|
|
11
|
+
class Seed
|
|
12
|
+
include Utils::Encoding
|
|
13
|
+
extend Utils::Encoding
|
|
14
|
+
|
|
15
|
+
# Raw seed bytes after normalization and validation.
|
|
16
|
+
#
|
|
17
|
+
# @return [String]
|
|
18
|
+
attr_reader :bytes
|
|
19
|
+
|
|
20
|
+
# @param bytes [String] raw seed bytes
|
|
21
|
+
# @raise [Errors::InvalidSeedError] if the seed length is not valid
|
|
22
|
+
def initialize(bytes)
|
|
23
|
+
@bytes = bytes
|
|
24
|
+
|
|
25
|
+
raise Errors::InvalidSeedError unless Constants::SEED_LENGTHS.include?(@bytes.bytesize)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the hex representation of the seed
|
|
29
|
+
#
|
|
30
|
+
# @return [String] hex string
|
|
31
|
+
def hex
|
|
32
|
+
bytes_to_hex(@bytes)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the array of octets (integers 0-255) representation of the seed
|
|
36
|
+
#
|
|
37
|
+
# @return [Array<Integer>] array of byte values
|
|
38
|
+
def octets
|
|
39
|
+
@bytes.bytes
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class << self
|
|
43
|
+
# Loads a seed from a given value
|
|
44
|
+
#
|
|
45
|
+
# Supported inputs:
|
|
46
|
+
# - `nil` to generate a new random seed
|
|
47
|
+
# - existing {Seed}
|
|
48
|
+
# - validated {Recovery::Bip39}
|
|
49
|
+
# - hex string
|
|
50
|
+
# - mnemonic string
|
|
51
|
+
# - raw byte string
|
|
52
|
+
# - array of octets
|
|
53
|
+
#
|
|
54
|
+
# @param value [String, Seed, Recovery::Bip39, Array<Integer>, nil]
|
|
55
|
+
# @return [Seed]
|
|
56
|
+
# @raise [Errors::InvalidSeedError] if the value cannot be normalized
|
|
57
|
+
def import(value)
|
|
58
|
+
case
|
|
59
|
+
when value.nil? then generate
|
|
60
|
+
when value.is_a?(Seed) then import_from_seed(value)
|
|
61
|
+
when value.is_a?(Recovery::Bip39) then import_from_mnemonic(value)
|
|
62
|
+
when hex_string?(value) then import_from_hex(value)
|
|
63
|
+
when mnemonic_string?(value) then import_from_mnemonic(value)
|
|
64
|
+
when byte_string?(value) then import_from_bytes(value)
|
|
65
|
+
when octet_array?(value) then import_from_octets(value)
|
|
66
|
+
else
|
|
67
|
+
raise Errors::InvalidSeedError
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Generates a new random 32-byte seed
|
|
72
|
+
#
|
|
73
|
+
# @return [Seed] the generated seed
|
|
74
|
+
def generate
|
|
75
|
+
new(Core::Entropy.generate(bytes: 32))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Creates a new seed from a byte string
|
|
79
|
+
#
|
|
80
|
+
# @param seed [String] the byte string seed
|
|
81
|
+
# @return [Seed] the created Seed
|
|
82
|
+
def import_from_bytes(bytes)
|
|
83
|
+
new(bytes)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Creates a new seed from a hex string
|
|
87
|
+
#
|
|
88
|
+
# @param seed_hex [String] the hex string seed
|
|
89
|
+
# @return [Seed] the created Seed
|
|
90
|
+
def import_from_hex(hex)
|
|
91
|
+
new(hex_to_bytes(hex))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Creates a new seed from an array of octets
|
|
95
|
+
#
|
|
96
|
+
# @param seed_octets [Array<Integer>] the array of octets
|
|
97
|
+
# @return [Seed] the created Seed
|
|
98
|
+
def import_from_octets(octets)
|
|
99
|
+
new(octets_to_bytes(octets))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Creates a new seed from another Seed
|
|
103
|
+
#
|
|
104
|
+
# @param seed [Seed] the Seed to copy
|
|
105
|
+
# @return [Seed] the created Seed
|
|
106
|
+
def import_from_seed(seed)
|
|
107
|
+
new(seed.bytes)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Recovers a seed from a BIP39 mnemonic phrase.
|
|
111
|
+
#
|
|
112
|
+
# @param mnemonic [Recovery::Bip39, String]
|
|
113
|
+
# @return [Seed]
|
|
114
|
+
def import_from_mnemonic(mnemonic)
|
|
115
|
+
Recovery::Bip39.import(mnemonic).seed
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def mnemonic_string?(value)
|
|
121
|
+
return false unless value.is_a?(String)
|
|
122
|
+
|
|
123
|
+
Constants::MNEMONIC_WORD_COUNTS.include?(value.strip.split(/\s+/).length)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SkeletonKey
|
|
4
|
+
module Utils
|
|
5
|
+
##
|
|
6
|
+
# Static helpers for encoding/decoding binary values
|
|
7
|
+
# into hex, base64, base58, etc.
|
|
8
|
+
#
|
|
9
|
+
# Also provides validation helpers for distinguishing
|
|
10
|
+
# between byte strings and hex strings.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# SkeletonKey::Utils::Encoding.hex_to_bytes("deadbeef")
|
|
14
|
+
# SkeletonKey::Utils::Encoding.bytes_to_hex("\xDE\xAD")
|
|
15
|
+
# SkeletonKey::Utils::Encoding.hex_string?("zzzz") # => false
|
|
16
|
+
#
|
|
17
|
+
module Encoding
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Base58Check encoding (Bitcoin alphabet)
|
|
22
|
+
#
|
|
23
|
+
# @param [String] payload binary string to encode
|
|
24
|
+
# @return [String] base58check-encoded string
|
|
25
|
+
def base58check_encode(payload)
|
|
26
|
+
Codecs::Base58Check.encode(payload)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Convert a hex string into raw bytes.
|
|
31
|
+
#
|
|
32
|
+
# @param [String] hex a valid hex string
|
|
33
|
+
# @return [String] binary string of bytes
|
|
34
|
+
# @raise [ArgumentError] if input is not valid hex
|
|
35
|
+
def hex_to_bytes(hex)
|
|
36
|
+
raise ArgumentError, "invalid hex string" unless hex_string?(hex)
|
|
37
|
+
[hex].pack("H*")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# Convert raw bytes into a hex string.
|
|
42
|
+
#
|
|
43
|
+
# @param [String] bytes binary string
|
|
44
|
+
# @return [String] hex string
|
|
45
|
+
def bytes_to_hex(bytes)
|
|
46
|
+
bytes.unpack1("H*")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Convert an array of octets (integers 0-255) into raw bytes.
|
|
51
|
+
#
|
|
52
|
+
# @param [Array<Integer>] octets array of byte values
|
|
53
|
+
# @return [String] binary string of bytes
|
|
54
|
+
def octets_to_bytes(octets)
|
|
55
|
+
octets.pack("C*")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Check if a value is valid hex.
|
|
60
|
+
#
|
|
61
|
+
# @param [String] value
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
def hex_string?(value)
|
|
64
|
+
value.is_a?(String) && value.match?(/\A[0-9a-fA-F]+\z/) && value.length.even?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Check if a value looks like a raw byte string.
|
|
69
|
+
#
|
|
70
|
+
# @param [String] value
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def byte_string?(value)
|
|
73
|
+
value.is_a?(String) && value.encoding == ::Encoding::BINARY
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Checks if a value is an array of octets (integers 0-255)
|
|
78
|
+
#
|
|
79
|
+
# @param [Object] value
|
|
80
|
+
# @return [Boolean]
|
|
81
|
+
def octet_array?(value)
|
|
82
|
+
value.is_a?(Array) && value.all? { |b| b.is_a?(Integer) && b.between?(0, 255) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Serialize a 32-bit unsigned integer to big-endian binary.
|
|
87
|
+
#
|
|
88
|
+
# Used in BIP32 child index encoding and serialization.
|
|
89
|
+
#
|
|
90
|
+
# @param i [Integer] integer in range 0..2^32-1
|
|
91
|
+
# @return [String] 4-byte big-endian representation
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# ser32(1) # => "\x00\x00\x00\x01"
|
|
95
|
+
# ser32(256) # => "\x00\x00\x01\x00"
|
|
96
|
+
def ser32(i)
|
|
97
|
+
[i].pack("N")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Serialize a 256-bit integer into a fixed-length 32-byte string.
|
|
102
|
+
#
|
|
103
|
+
# Pads with leading zeros if necessary. Used for private key
|
|
104
|
+
# and scalar encoding in BIP32.
|
|
105
|
+
#
|
|
106
|
+
# @param i [Integer] integer in range 0..2^256-1
|
|
107
|
+
# @return [String] 32-byte binary string
|
|
108
|
+
#
|
|
109
|
+
# @example
|
|
110
|
+
# ser256(1).bytesize # => 32
|
|
111
|
+
# ser256(1).unpack1("H*") # => "0000...0001" (64 hex chars)
|
|
112
|
+
def ser256(i)
|
|
113
|
+
i.to_s(16).rjust(64, "0").scan(/../).map { |b| b.hex }.pack("C*")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Parse a 32-byte string into a 256-bit integer.
|
|
118
|
+
#
|
|
119
|
+
# Inverse of {#ser256}. Converts a big-endian binary
|
|
120
|
+
# string into an Integer.
|
|
121
|
+
#
|
|
122
|
+
# @param b [String] 32-byte binary string
|
|
123
|
+
# @return [Integer] integer value
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# i = 42
|
|
127
|
+
# bin = ser256(i)
|
|
128
|
+
# parse256(bin) # => 42
|
|
129
|
+
def parse256(b)
|
|
130
|
+
b.unpack1("H*").to_i(16)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "openssl"
|
|
5
|
+
|
|
6
|
+
module SkeletonKey
|
|
7
|
+
module Utils
|
|
8
|
+
##
|
|
9
|
+
# Collection of cryptographic hashing and HMAC utilities.
|
|
10
|
+
#
|
|
11
|
+
# This module wraps common digest primitives into clear,
|
|
12
|
+
# chainable methods with explicit names.
|
|
13
|
+
#
|
|
14
|
+
# These are the **building blocks** for higher-level functions
|
|
15
|
+
# like address encoding (Bitcoin), key derivation (BIP32/SLIP-10),
|
|
16
|
+
# and message authentication.
|
|
17
|
+
#
|
|
18
|
+
# @example Compute a Bitcoin address hash
|
|
19
|
+
# pubkey = "\x02..." # compressed secp256k1 public key
|
|
20
|
+
# addr_hash = SkeletonKey::Hashing.hash160(pubkey)
|
|
21
|
+
#
|
|
22
|
+
# @example Compute a BIP32 master key
|
|
23
|
+
# seed = SecureRandom.random_bytes(64)
|
|
24
|
+
# i = SkeletonKey::Hashing.hmac_sha512("Bitcoin seed", seed)
|
|
25
|
+
#
|
|
26
|
+
module Hashing
|
|
27
|
+
module_function
|
|
28
|
+
##
|
|
29
|
+
# Generate n-byte checksum (first n bytes of double SHA256)
|
|
30
|
+
#
|
|
31
|
+
# @param payload [String] input byte string
|
|
32
|
+
# @return [String] n-byte checksum
|
|
33
|
+
def checksum(payload, length: 4)
|
|
34
|
+
double_sha256(payload)[0, length]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Single SHA-256 digest of data.
|
|
39
|
+
#
|
|
40
|
+
# @param data [String] input byte string
|
|
41
|
+
# @return [String] 32-byte binary digest
|
|
42
|
+
#
|
|
43
|
+
# @see https://en.wikipedia.org/wiki/SHA-2
|
|
44
|
+
def sha256(data)
|
|
45
|
+
Digest::SHA256.digest(data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Single RIPEMD-160 digest of data.
|
|
50
|
+
#
|
|
51
|
+
# @param data [String] input byte string
|
|
52
|
+
# @return [String] 20-byte binary digest
|
|
53
|
+
#
|
|
54
|
+
# @see https://homes.esat.kuleuven.be/~bosselae/ripemd160/
|
|
55
|
+
def ripemd160(data)
|
|
56
|
+
Digest::RMD160.digest(data)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Hash160 = RIPEMD-160(SHA-256(data))
|
|
61
|
+
#
|
|
62
|
+
# Widely used in Bitcoin for address derivation from public keys.
|
|
63
|
+
#
|
|
64
|
+
# @param data [String] input byte string
|
|
65
|
+
# @return [String] 20-byte binary digest
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# SkeletonKey::Hashing.hash160(pubkey)
|
|
69
|
+
def hash160(data)
|
|
70
|
+
ripemd160(sha256(data))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Double SHA-256 digest.
|
|
75
|
+
#
|
|
76
|
+
# Used in Bitcoin for checksums and block header hashing.
|
|
77
|
+
#
|
|
78
|
+
# @param data [String] input byte string
|
|
79
|
+
# @return [String] 32-byte binary digest
|
|
80
|
+
#
|
|
81
|
+
# @example
|
|
82
|
+
# SkeletonKey::Hashing.double_sha256("hello")
|
|
83
|
+
def double_sha256(data)
|
|
84
|
+
sha256(sha256(data))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# HMAC-SHA512 keyed hash.
|
|
89
|
+
#
|
|
90
|
+
# Used in BIP32 master key and child key derivation.
|
|
91
|
+
#
|
|
92
|
+
# @param key [String] HMAC key
|
|
93
|
+
# @param data [String] message input
|
|
94
|
+
# @return [String] 64-byte binary HMAC digest
|
|
95
|
+
#
|
|
96
|
+
# @see https://en.wikipedia.org/wiki/HMAC
|
|
97
|
+
# @see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
|
98
|
+
#
|
|
99
|
+
# @example BIP32 master key
|
|
100
|
+
# i = SkeletonKey::Hashing.hmac_sha512("Bitcoin seed", seed)
|
|
101
|
+
# il, ir = i[0, 32], i[32, 32]
|
|
102
|
+
def hmac_sha512(key, data)
|
|
103
|
+
OpenSSL::HMAC.digest("SHA512", key, data)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def keccak256(data)
|
|
107
|
+
rate = 136
|
|
108
|
+
state = Array.new(25, 0)
|
|
109
|
+
offset = 0
|
|
110
|
+
|
|
111
|
+
while offset + rate <= data.bytesize
|
|
112
|
+
keccak_absorb_block(state, data.byteslice(offset, rate))
|
|
113
|
+
keccak_f1600(state)
|
|
114
|
+
offset += rate
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
tail = (data.byteslice(offset, data.bytesize - offset) || +"").b
|
|
118
|
+
tail << "\x01".b
|
|
119
|
+
tail << "\x00".b * (rate - tail.bytesize)
|
|
120
|
+
tail.setbyte(rate - 1, tail.getbyte(rate - 1) | 0x80)
|
|
121
|
+
|
|
122
|
+
keccak_absorb_block(state, tail)
|
|
123
|
+
keccak_f1600(state)
|
|
124
|
+
keccak_squeeze(state, 32)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def keccak_absorb_block(state, block)
|
|
128
|
+
(block.bytesize / 8).times do |idx|
|
|
129
|
+
lane_bytes = block.byteslice(idx * 8, 8).bytes
|
|
130
|
+
lane = lane_bytes.each_with_index.reduce(0) do |acc, (byte, byte_idx)|
|
|
131
|
+
acc | (byte << (8 * byte_idx))
|
|
132
|
+
end
|
|
133
|
+
state[idx] ^= lane
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def keccak_squeeze(state, length)
|
|
138
|
+
output = +"".b
|
|
139
|
+
lane_index = 0
|
|
140
|
+
|
|
141
|
+
while output.bytesize < length
|
|
142
|
+
output << [state[lane_index]].pack("Q<")
|
|
143
|
+
lane_index += 1
|
|
144
|
+
if lane_index == 17 && output.bytesize < length
|
|
145
|
+
keccak_f1600(state)
|
|
146
|
+
lane_index = 0
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
output.byteslice(0, length)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def keccak_f1600(state)
|
|
154
|
+
24.times do |round|
|
|
155
|
+
c = 5.times.map do |x|
|
|
156
|
+
state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
d = 5.times.map do |x|
|
|
160
|
+
c[(x - 1) % 5] ^ keccak_rotl64(c[(x + 1) % 5], 1)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
25.times do |idx|
|
|
164
|
+
state[idx] = (state[idx] ^ d[idx % 5]) & keccak_mask
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
b = Array.new(25, 0)
|
|
168
|
+
5.times do |x|
|
|
169
|
+
5.times do |y|
|
|
170
|
+
b[y + (5 * ((2 * x + 3 * y) % 5))] =
|
|
171
|
+
keccak_rotl64(state[x + (5 * y)], keccak_rotation_offsets[x][y])
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
5.times do |x|
|
|
176
|
+
5.times do |y|
|
|
177
|
+
idx = x + (5 * y)
|
|
178
|
+
state[idx] = b[idx] ^ ((~b[((x + 1) % 5) + (5 * y)]) & b[((x + 2) % 5) + (5 * y)])
|
|
179
|
+
state[idx] &= keccak_mask
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
state[0] ^= keccak_round_constants[round]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def keccak_rotl64(value, shift)
|
|
188
|
+
shift %= 64
|
|
189
|
+
return value & keccak_mask if shift.zero?
|
|
190
|
+
|
|
191
|
+
((value << shift) | (value >> (64 - shift))) & keccak_mask
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def keccak_mask
|
|
195
|
+
0xFFFF_FFFF_FFFF_FFFF
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def keccak_rotation_offsets
|
|
199
|
+
[
|
|
200
|
+
[0, 36, 3, 41, 18],
|
|
201
|
+
[1, 44, 10, 45, 2],
|
|
202
|
+
[62, 6, 43, 15, 61],
|
|
203
|
+
[28, 55, 25, 21, 56],
|
|
204
|
+
[27, 20, 39, 8, 14]
|
|
205
|
+
]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def keccak_round_constants
|
|
209
|
+
[
|
|
210
|
+
0x0000000000000001,
|
|
211
|
+
0x0000000000008082,
|
|
212
|
+
0x800000000000808A,
|
|
213
|
+
0x8000000080008000,
|
|
214
|
+
0x000000000000808B,
|
|
215
|
+
0x0000000080000001,
|
|
216
|
+
0x8000000080008081,
|
|
217
|
+
0x8000000000008009,
|
|
218
|
+
0x000000000000008A,
|
|
219
|
+
0x0000000000000088,
|
|
220
|
+
0x0000000080008009,
|
|
221
|
+
0x000000008000000A,
|
|
222
|
+
0x000000008000808B,
|
|
223
|
+
0x800000000000008B,
|
|
224
|
+
0x8000000000008089,
|
|
225
|
+
0x8000000000008003,
|
|
226
|
+
0x8000000000008002,
|
|
227
|
+
0x8000000000000080,
|
|
228
|
+
0x000000000000800A,
|
|
229
|
+
0x800000008000000A,
|
|
230
|
+
0x8000000080008081,
|
|
231
|
+
0x8000000000008080,
|
|
232
|
+
0x0000000080000001,
|
|
233
|
+
0x8000000080008008
|
|
234
|
+
]
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
data/lib/skeleton_key.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Version
|
|
2
|
+
require_relative "skeleton_key/version"
|
|
3
|
+
|
|
4
|
+
# Main module for SkeletonKey
|
|
5
|
+
require_relative "skeleton_key/constants"
|
|
6
|
+
require_relative "skeleton_key/errors"
|
|
7
|
+
|
|
8
|
+
# Utilities
|
|
9
|
+
require_relative "skeleton_key/utils/hashing"
|
|
10
|
+
require_relative "skeleton_key/utils/encoding"
|
|
11
|
+
require_relative "skeleton_key/codecs/base58"
|
|
12
|
+
require_relative "skeleton_key/codecs/base58_check"
|
|
13
|
+
require_relative "skeleton_key/codecs/bech32"
|
|
14
|
+
|
|
15
|
+
# Derivation methods
|
|
16
|
+
require_relative "skeleton_key/derivation/path"
|
|
17
|
+
require_relative "skeleton_key/derivation/bip32"
|
|
18
|
+
require_relative "skeleton_key/derivation/slip10"
|
|
19
|
+
|
|
20
|
+
# Core functionality
|
|
21
|
+
require_relative "skeleton_key/core/entropy"
|
|
22
|
+
require_relative "skeleton_key/recovery/slip39_support/protocol"
|
|
23
|
+
require_relative "skeleton_key/recovery/slip39_support/share"
|
|
24
|
+
require_relative "skeleton_key/recovery/slip39_support/bit_packing"
|
|
25
|
+
require_relative "skeleton_key/recovery/slip39_support/checksum"
|
|
26
|
+
require_relative "skeleton_key/recovery/slip39_support/interpolation"
|
|
27
|
+
require_relative "skeleton_key/recovery/slip39_support/cipher"
|
|
28
|
+
require_relative "skeleton_key/recovery/slip39_support/encoder"
|
|
29
|
+
require_relative "skeleton_key/recovery/slip39_support/generated_set"
|
|
30
|
+
require_relative "skeleton_key/recovery/slip39_support/decoder"
|
|
31
|
+
require_relative "skeleton_key/recovery/slip39_support/secret_recovery"
|
|
32
|
+
require_relative "skeleton_key/recovery/slip39_support/generator"
|
|
33
|
+
require_relative "skeleton_key/recovery/slip39"
|
|
34
|
+
|
|
35
|
+
# Higher-level abstractions
|
|
36
|
+
require_relative "skeleton_key/recovery/bip39"
|
|
37
|
+
require_relative "skeleton_key/seed"
|
|
38
|
+
require_relative "skeleton_key/keyring"
|
|
39
|
+
|
|
40
|
+
# Canonical chain namespace
|
|
41
|
+
require_relative "skeleton_key/chains/bitcoin/support/versioning"
|
|
42
|
+
require_relative "skeleton_key/chains/bitcoin/support/paths"
|
|
43
|
+
require_relative "skeleton_key/chains/bitcoin/support/outputs"
|
|
44
|
+
require_relative "skeleton_key/chains/bitcoin/support"
|
|
45
|
+
require_relative "skeleton_key/chains/bitcoin/account_derivation"
|
|
46
|
+
require_relative "skeleton_key/chains/bitcoin/account"
|
|
47
|
+
require_relative "skeleton_key/chains/ethereum/support"
|
|
48
|
+
require_relative "skeleton_key/chains/ethereum/account"
|
|
49
|
+
require_relative "skeleton_key/chains/solana/support"
|
|
50
|
+
require_relative "skeleton_key/chains/solana/account"
|
|
51
|
+
|
|
52
|
+
module SkeletonKey
|
|
53
|
+
##
|
|
54
|
+
# Top-level namespace for the SkeletonKey library.
|
|
55
|
+
#
|
|
56
|
+
# The file load order here reflects the repository architecture:
|
|
57
|
+
# - shared constants and typed errors
|
|
58
|
+
# - shared utilities and codecs
|
|
59
|
+
# - shared derivation primitives
|
|
60
|
+
# - recovery formats and seed normalization
|
|
61
|
+
# - chain-specific account implementations
|
|
62
|
+
class Error < StandardError; end
|
|
63
|
+
|
|
64
|
+
# Project root directory
|
|
65
|
+
ROOT = File.expand_path("..", __dir__)
|
|
66
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: skeleton_key
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sebastian Scholl
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rspec
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.12'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.12'
|
|
26
|
+
description: SkeletonKey provides deterministic wallet recovery, seed normalization,
|
|
27
|
+
and key derivation for Bitcoin, Ethereum, and Solana.
|
|
28
|
+
email:
|
|
29
|
+
- sebscholl@gmail.com
|
|
30
|
+
executables:
|
|
31
|
+
- console
|
|
32
|
+
- lint
|
|
33
|
+
- setup
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- README.md
|
|
38
|
+
- bin/console
|
|
39
|
+
- bin/lint
|
|
40
|
+
- bin/setup
|
|
41
|
+
- lib/skeleton_key.rb
|
|
42
|
+
- lib/skeleton_key/chains/bitcoin/account.rb
|
|
43
|
+
- lib/skeleton_key/chains/bitcoin/account_derivation.rb
|
|
44
|
+
- lib/skeleton_key/chains/bitcoin/support.rb
|
|
45
|
+
- lib/skeleton_key/chains/bitcoin/support/outputs.rb
|
|
46
|
+
- lib/skeleton_key/chains/bitcoin/support/paths.rb
|
|
47
|
+
- lib/skeleton_key/chains/bitcoin/support/versioning.rb
|
|
48
|
+
- lib/skeleton_key/chains/ethereum/account.rb
|
|
49
|
+
- lib/skeleton_key/chains/ethereum/support.rb
|
|
50
|
+
- lib/skeleton_key/chains/solana/account.rb
|
|
51
|
+
- lib/skeleton_key/chains/solana/support.rb
|
|
52
|
+
- lib/skeleton_key/codecs/base58.rb
|
|
53
|
+
- lib/skeleton_key/codecs/base58_check.rb
|
|
54
|
+
- lib/skeleton_key/codecs/bech32.rb
|
|
55
|
+
- lib/skeleton_key/constants.rb
|
|
56
|
+
- lib/skeleton_key/core/entropy.rb
|
|
57
|
+
- lib/skeleton_key/derivation/bip32.rb
|
|
58
|
+
- lib/skeleton_key/derivation/path.rb
|
|
59
|
+
- lib/skeleton_key/derivation/slip10.rb
|
|
60
|
+
- lib/skeleton_key/errors.rb
|
|
61
|
+
- lib/skeleton_key/keyring.rb
|
|
62
|
+
- lib/skeleton_key/recovery/bip39.rb
|
|
63
|
+
- lib/skeleton_key/recovery/bip39_english.txt
|
|
64
|
+
- lib/skeleton_key/recovery/slip39.rb
|
|
65
|
+
- lib/skeleton_key/recovery/slip39_support/bit_packing.rb
|
|
66
|
+
- lib/skeleton_key/recovery/slip39_support/checksum.rb
|
|
67
|
+
- lib/skeleton_key/recovery/slip39_support/cipher.rb
|
|
68
|
+
- lib/skeleton_key/recovery/slip39_support/decoder.rb
|
|
69
|
+
- lib/skeleton_key/recovery/slip39_support/encoder.rb
|
|
70
|
+
- lib/skeleton_key/recovery/slip39_support/generated_set.rb
|
|
71
|
+
- lib/skeleton_key/recovery/slip39_support/generator.rb
|
|
72
|
+
- lib/skeleton_key/recovery/slip39_support/interpolation.rb
|
|
73
|
+
- lib/skeleton_key/recovery/slip39_support/protocol.rb
|
|
74
|
+
- lib/skeleton_key/recovery/slip39_support/secret_recovery.rb
|
|
75
|
+
- lib/skeleton_key/recovery/slip39_support/share.rb
|
|
76
|
+
- lib/skeleton_key/recovery/slip39_wordlist.txt
|
|
77
|
+
- lib/skeleton_key/seed.rb
|
|
78
|
+
- lib/skeleton_key/skeleton_key.code-workspace
|
|
79
|
+
- lib/skeleton_key/utils/encoding.rb
|
|
80
|
+
- lib/skeleton_key/utils/hashing.rb
|
|
81
|
+
- lib/skeleton_key/version.rb
|
|
82
|
+
homepage: https://github.com/sebscholl/skeleton-key
|
|
83
|
+
licenses:
|
|
84
|
+
- MIT
|
|
85
|
+
metadata:
|
|
86
|
+
homepage_uri: https://github.com/sebscholl/skeleton-key
|
|
87
|
+
source_code_uri: https://github.com/sebscholl/skeleton-key
|
|
88
|
+
documentation_uri: https://github.com/sebscholl/skeleton-key#readme
|
|
89
|
+
rubygems_mfa_required: 'true'
|
|
90
|
+
rdoc_options: []
|
|
91
|
+
require_paths:
|
|
92
|
+
- lib
|
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: 3.2.0
|
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
requirements: []
|
|
104
|
+
rubygems_version: 3.6.9
|
|
105
|
+
specification_version: 4
|
|
106
|
+
summary: Deterministic wallet recovery and derivation across chains
|
|
107
|
+
test_files: []
|