Module: Klay::Signature
Overview
Defines handy tools for verifying and recovering signatures.
Defined Under Namespace
Classes: SignatureError
Constant Summary collapse
- EIP191_PREFIX_BYTE =
EIP-191 prefix byte 0x19
"\x19".freeze
- EIP712_VERSION_BYTE =
EIP-712 version byte 0x01
"\x01".freeze
Class Method Summary collapse
-
.dissect(signature) ⇒ String
Dissects a signature blob of 65+ bytes into its
r
,s
, andv
values. -
.personal_recover(message, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a prefixed, personal message and a signature on a given chain.
-
.prefix_message(message) ⇒ String
Prefix message as per EIP-191 with
0x19
to ensure the data is not valid RLP and thus not mistaken for a transaction. -
.recover(blob, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a signature from arbitrary data without validation on a given chain.
-
.recover_typed_data(typed_data, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a typed data structure and a signature on a given chain.
-
.verify(blob, signature, public_key, chain_id = Chain::CYPRESS) ⇒ Boolean
Verifies a signature for a given public key or address.
Instance Method Summary collapse
-
#dissect(signature) ⇒ String
Dissects a signature blob of 65+ bytes into its
r
,s
, andv
values. -
#personal_recover(message, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a prefixed, personal message and a signature on a given chain.
-
#prefix_message(message) ⇒ String
Prefix message as per EIP-191 with
0x19
to ensure the data is not valid RLP and thus not mistaken for a transaction. -
#recover(blob, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a signature from arbitrary data without validation on a given chain.
-
#recover_typed_data(typed_data, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a typed data structure and a signature on a given chain.
-
#verify(blob, signature, public_key, chain_id = Chain::CYPRESS) ⇒ Boolean
Verifies a signature for a given public key or address.
Class Method Details
.dissect(signature) ⇒ String
Dissects a signature blob of 65+ bytes into its r
, s
, and v
values.
50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/klay/signature.rb', line 50 def dissect(signature) signature = Util.bin_to_hex signature unless Util.is_hex? signature signature = Util.remove_hex_prefix signature if signature.size != 130 raise SignatureError, "Unknown signature length #{signature.size}!" end r = signature[0...64] s = signature[64...128] v = signature[128..] return r, s, v end |
.personal_recover(message, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a prefixed, personal message and a signature on a given chain. (EIP-191) Ref: https://eips.ethereum.org/EIPS/eip-191
89 90 91 92 93 |
# File 'lib/klay/signature.rb', line 89 def personal_recover(, signature, chain_id = Chain::CYPRESS) = = Util.keccak256 recover , signature, chain_id end |
.prefix_message(message) ⇒ String
Prefix message as per EIP-191 with 0x19
to ensure the data is not
valid RLP and thus not mistaken for a transaction.
EIP-191 Version byte: 0x45
(E
)
Ref: https://eips.ethereum.org/EIPS/eip-191
40 41 42 |
# File 'lib/klay/signature.rb', line 40 def () "\u0019Klaytn Signed Message:\n#{.size}#{}" end |
.recover(blob, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a signature from arbitrary data without validation on a given chain.
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/klay/signature.rb', line 69 def recover(blob, signature, chain_id = Chain::CYPRESS) context = Secp256k1::Context.new r, s, v = dissect signature v = v.to_i(16) # raise SignatureError, "Invalid signature v byte #{v} for chain ID #{chain_id}!" if v != 130 recovery_id = Chain.to_recovery_id v, chain_id signature_rs = Util.hex_to_bin "#{r}#{s}" recoverable_signature = context.recoverable_signature_from_compact signature_rs, recovery_id public_key = recoverable_signature.recover_public_key blob Util.bin_to_hex public_key.uncompressed end |
.recover_typed_data(typed_data, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a typed data structure and a signature on a given chain. (EIP-712) Ref: https://eips.ethereum.org/EIPS/eip-712
103 104 105 106 |
# File 'lib/klay/signature.rb', line 103 def recover_typed_data(typed_data, signature, chain_id = Chain::CYPRESS) hash_to_sign = Eip712.hash typed_data recover hash_to_sign, signature, chain_id end |
.verify(blob, signature, public_key, chain_id = Chain::CYPRESS) ⇒ Boolean
Verifies a signature for a given public key or address.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/klay/signature.rb', line 116 def verify(blob, signature, public_key, chain_id = Chain::CYPRESS) recovered_key = nil if blob.instance_of? Array or blob.instance_of? Hash # recover Array from sign_typed_data recovered_key = recover_typed_data blob, signature, chain_id elsif blob.instance_of? String and blob.encoding != Encoding::ASCII_8BIT # recover message from personal_sign recovered_key = personal_recover blob, signature, chain_id elsif blob.instance_of? String and (Util.is_hex? blob or blob.encoding == Encoding::ASCII_8BIT) # if nothing else, recover from arbitrary signature recovered_key = recover blob, signature, chain_id end # raise if we cannot determine the data format raise SignatureError, "Unknown data format to verify: #{blob}" if recovered_key.nil? if public_key.instance_of? Address # recovering using an Klay::Address address = public_key.to_s recovered_address = Util.public_key_to_address(recovered_key).to_s return address == recovered_address elsif public_key.instance_of? Secp256k1::PublicKey # recovering using an Secp256k1::PublicKey public_hex = Util.bin_to_hex public_key.uncompressed return public_hex == recovered_key elsif public_key.size == 42 # recovering using an address String address = Address.new(public_key).to_s recovered_address = Util.public_key_to_address(recovered_key).to_s return address == recovered_address elsif public_key.size == 130 # recovering using an uncompressed public key String return public_key == recovered_key else # raise if we cannot determine the public key format used raise SignatureError, "Invalid public key or address supplied #{public_key}!" end end |
Instance Method Details
#dissect(signature) ⇒ String
Dissects a signature blob of 65+ bytes into its r
, s
, and v
values.
50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/klay/signature.rb', line 50 def dissect(signature) signature = Util.bin_to_hex signature unless Util.is_hex? signature signature = Util.remove_hex_prefix signature if signature.size != 130 raise SignatureError, "Unknown signature length #{signature.size}!" end r = signature[0...64] s = signature[64...128] v = signature[128..] return r, s, v end |
#personal_recover(message, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a prefixed, personal message and a signature on a given chain. (EIP-191) Ref: https://eips.ethereum.org/EIPS/eip-191
89 90 91 92 93 |
# File 'lib/klay/signature.rb', line 89 def personal_recover(, signature, chain_id = Chain::CYPRESS) = = Util.keccak256 recover , signature, chain_id end |
#prefix_message(message) ⇒ String
Prefix message as per EIP-191 with 0x19
to ensure the data is not
valid RLP and thus not mistaken for a transaction.
EIP-191 Version byte: 0x45
(E
)
Ref: https://eips.ethereum.org/EIPS/eip-191
40 41 42 |
# File 'lib/klay/signature.rb', line 40 def () "\u0019Klaytn Signed Message:\n#{.size}#{}" end |
#recover(blob, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a signature from arbitrary data without validation on a given chain.
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/klay/signature.rb', line 69 def recover(blob, signature, chain_id = Chain::CYPRESS) context = Secp256k1::Context.new r, s, v = dissect signature v = v.to_i(16) # raise SignatureError, "Invalid signature v byte #{v} for chain ID #{chain_id}!" if v != 130 recovery_id = Chain.to_recovery_id v, chain_id signature_rs = Util.hex_to_bin "#{r}#{s}" recoverable_signature = context.recoverable_signature_from_compact signature_rs, recovery_id public_key = recoverable_signature.recover_public_key blob Util.bin_to_hex public_key.uncompressed end |
#recover_typed_data(typed_data, signature, chain_id = Chain::CYPRESS) ⇒ String
Recovers a public key from a typed data structure and a signature on a given chain. (EIP-712) Ref: https://eips.ethereum.org/EIPS/eip-712
103 104 105 106 |
# File 'lib/klay/signature.rb', line 103 def recover_typed_data(typed_data, signature, chain_id = Chain::CYPRESS) hash_to_sign = Eip712.hash typed_data recover hash_to_sign, signature, chain_id end |
#verify(blob, signature, public_key, chain_id = Chain::CYPRESS) ⇒ Boolean
Verifies a signature for a given public key or address.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/klay/signature.rb', line 116 def verify(blob, signature, public_key, chain_id = Chain::CYPRESS) recovered_key = nil if blob.instance_of? Array or blob.instance_of? Hash # recover Array from sign_typed_data recovered_key = recover_typed_data blob, signature, chain_id elsif blob.instance_of? String and blob.encoding != Encoding::ASCII_8BIT # recover message from personal_sign recovered_key = personal_recover blob, signature, chain_id elsif blob.instance_of? String and (Util.is_hex? blob or blob.encoding == Encoding::ASCII_8BIT) # if nothing else, recover from arbitrary signature recovered_key = recover blob, signature, chain_id end # raise if we cannot determine the data format raise SignatureError, "Unknown data format to verify: #{blob}" if recovered_key.nil? if public_key.instance_of? Address # recovering using an Klay::Address address = public_key.to_s recovered_address = Util.public_key_to_address(recovered_key).to_s return address == recovered_address elsif public_key.instance_of? Secp256k1::PublicKey # recovering using an Secp256k1::PublicKey public_hex = Util.bin_to_hex public_key.uncompressed return public_hex == recovered_key elsif public_key.size == 42 # recovering using an address String address = Address.new(public_key).to_s recovered_address = Util.public_key_to_address(recovered_key).to_s return address == recovered_address elsif public_key.size == 130 # recovering using an uncompressed public key String return public_key == recovered_key else # raise if we cannot determine the public key format used raise SignatureError, "Invalid public key or address supplied #{public_key}!" end end |