Module: Runar::Builtins
- Included in:
- Runar, SmartContract
- Defined in:
- lib/runar/builtins.rb
Constant Summary collapse
- TEST_MESSAGE_DIGEST =
The fixed test message digest shared across all Runar SDKs. SHA256(“runar-test-message-v1”) — matches Python TEST_MESSAGE_DIGEST.
Digest::SHA256.hexdigest('runar-test-message-v1').freeze
- SHA256_K =
SHA-256 round constants (first 32 bits of the fractional parts of the cube roots of the first 64 primes).
[ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ].freeze
Instance Method Summary collapse
-
#assert(condition) ⇒ Object
Runar assertion.
-
#bin2num(data) ⇒ Object
Convert a little-endian sign-magnitude hex string to an integer.
-
#blake3_compress(_chaining_value, _block) ⇒ Object
Mock BLAKE3 single-block compression.
-
#blake3_hash(_message) ⇒ Object
Mock BLAKE3 hash for messages up to 64 bytes.
- #bool(n) ⇒ Object
-
#cat(a, b) ⇒ Object
Concatenate two hex strings.
-
#check_multi_sig(sigs, pks) ⇒ Object
Verify multiple ECDSA signatures (Bitcoin-style ordered multi-sig).
- #check_preimage(_preimage) ⇒ Object
-
#check_sig(sig, pk) ⇒ Object
Verify an ECDSA signature over the fixed TEST_MESSAGE_DIGEST.
- #clamp(value, lo, hi) ⇒ Object
-
#divmod(a, b) ⇒ Object
Returns quotient only (matching Runar’s divmod which returns quotient).
- #extract_amount(_preimage) ⇒ Object
-
#extract_hash_prevouts(_preimage) ⇒ Object
Returns hash256(72 zero bytes) in test mode.
-
#extract_locktime(_preimage) ⇒ Object
– Mock Preimage Extraction ———————————————-.
- #extract_outpoint(_preimage) ⇒ Object
-
#extract_output_hash(preimage) ⇒ Object
Returns the first 32 bytes of the preimage as a hex string.
- #extract_sequence(_preimage) ⇒ Object
- #extract_version(_preimage) ⇒ Object
- #gcd(a, b) ⇒ Object
-
#hash160(data) ⇒ Object
RIPEMD160(SHA256(data)).
-
#hash256(data) ⇒ Object
SHA256(SHA256(data)).
-
#left(data, length) ⇒ Object
Left-most bytes of a hex string.
-
#len(data) ⇒ Object
Byte length of a hex string (hex.length / 2).
- #log2(n) ⇒ Object
- #mock_preimage ⇒ Object
-
#mock_pub_key ⇒ Object
Return ALICE’s real compressed secp256k1 public key (33 bytes, hex).
-
#mock_sig ⇒ Object
Return ALICE’s real ECDSA test signature (DER-encoded hex).
- #mul_div(a, b, c) ⇒ Object
-
#num2bin(v, length) ⇒ Object
Convert an integer to a little-endian sign-magnitude hex string of the specified byte length.
- #percent_of(amount, bps) ⇒ Object
- #pow(base, exp) ⇒ Object
-
#reverse_bytes(data) ⇒ Object
Reverse the byte order of a hex string.
-
#right(data, length) ⇒ Object
Right-most bytes of a hex string.
-
#ripemd160(data) ⇒ Object
RIPEMD-160 hash.
-
#safediv(a, b) ⇒ Object
– Math Utilities ——————————————————–.
- #safemod(a, b) ⇒ Object
-
#sha256(data) ⇒ Object
SHA-256 hash.
-
#sha256_compress(state, block) ⇒ Object
SHA-256 single-block compression function (FIPS 180-4 Section 6.2.2).
-
#sha256_finalize(state, remaining, msg_bit_len) ⇒ Object
SHA-256 finalization with FIPS 180-4 padding.
- #sign(n) ⇒ Object
-
#sqrt(n) ⇒ Object
Integer square root using Newton’s method.
-
#substr(data, start, length) ⇒ Object
Extract a substring of bytes from a hex string.
-
#verify_rabin_sig(msg, sig, padding, pk) ⇒ Object
Verify a Rabin signature.
- #verify_slh_dsa_sha2_128f(_msg, _sig, _pubkey) ⇒ Object
- #verify_slh_dsa_sha2_128s(_msg, _sig, _pubkey) ⇒ Object
- #verify_slh_dsa_sha2_192f(_msg, _sig, _pubkey) ⇒ Object
- #verify_slh_dsa_sha2_192s(_msg, _sig, _pubkey) ⇒ Object
- #verify_slh_dsa_sha2_256f(_msg, _sig, _pubkey) ⇒ Object
- #verify_slh_dsa_sha2_256s(_msg, _sig, _pubkey) ⇒ Object
- #verify_wots(_msg, _sig, _pubkey) ⇒ Object
- #within(x, lo, hi) ⇒ Object
Instance Method Details
#assert(condition) ⇒ Object
Runar assertion. Raises RuntimeError if condition is falsey.
22 23 24 |
# File 'lib/runar/builtins.rb', line 22 def assert(condition) raise 'runar: assertion failed' unless condition end |
#bin2num(data) ⇒ Object
Convert a little-endian sign-magnitude hex string to an integer. This is Bitcoin Script’s number encoding (sign bit in MSB of last byte).
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/runar/builtins.rb', line 460 def bin2num(data) return 0 if data.nil? || data.empty? raw = [data].pack('H*') bytes = raw.bytes return 0 if bytes.empty? negative = (bytes[-1] & 0x80) != 0 bytes[-1] &= 0x7F result = 0 bytes.each_with_index do |b, i| result |= (b << (8 * i)) end negative ? -result : result end |
#blake3_compress(_chaining_value, _block) ⇒ Object
Mock BLAKE3 single-block compression. In compiled Bitcoin Script this expands to ~10,000 opcodes. Returns 32 zero bytes as hex for business-logic testing.
228 229 230 |
# File 'lib/runar/builtins.rb', line 228 def blake3_compress(_chaining_value, _block) '00' * 32 end |
#blake3_hash(_message) ⇒ Object
Mock BLAKE3 hash for messages up to 64 bytes. In compiled Bitcoin Script this uses the IV as the chaining value and applies zero-padding before calling the compression function. Returns 32 zero bytes as hex for business-logic testing.
236 237 238 |
# File 'lib/runar/builtins.rb', line 236 def blake3_hash() '00' * 32 end |
#bool(n) ⇒ Object
390 391 392 |
# File 'lib/runar/builtins.rb', line 390 def bool(n) n != 0 end |
#cat(a, b) ⇒ Object
Concatenate two hex strings.
407 408 409 |
# File 'lib/runar/builtins.rb', line 407 def cat(a, b) a + b end |
#check_multi_sig(sigs, pks) ⇒ Object
Verify multiple ECDSA signatures (Bitcoin-style ordered multi-sig). Each signature must verify against the next unused public key in order. Both sigs and pks are arrays of hex-encoded strings.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/runar/builtins.rb', line 37 def check_multi_sig(sigs, pks) return false if sigs.length > pks.length pk_idx = 0 sigs.each do |sig| matched = false while pk_idx < pks.length if check_sig(sig, pks[pk_idx]) pk_idx += 1 matched = true break end pk_idx += 1 end return false unless matched end true end |
#check_preimage(_preimage) ⇒ Object
56 57 58 |
# File 'lib/runar/builtins.rb', line 56 def check_preimage(_preimage) true end |
#check_sig(sig, pk) ⇒ Object
Verify an ECDSA signature over the fixed TEST_MESSAGE_DIGEST. Both sig and pk are hex-encoded strings.
30 31 32 |
# File 'lib/runar/builtins.rb', line 30 def check_sig(sig, pk) Runar::ECDSA.verify(TEST_MESSAGE_DIGEST, sig, pk) end |
#clamp(value, lo, hi) ⇒ Object
332 333 334 335 336 337 |
# File 'lib/runar/builtins.rb', line 332 def clamp(value, lo, hi) return lo if value < lo return hi if value > hi value end |
#divmod(a, b) ⇒ Object
Returns quotient only (matching Runar’s divmod which returns quotient).
380 381 382 |
# File 'lib/runar/builtins.rb', line 380 def divmod(a, b) a / b end |
#extract_amount(_preimage) ⇒ Object
286 287 288 |
# File 'lib/runar/builtins.rb', line 286 def extract_amount(_preimage) 10_000 end |
#extract_hash_prevouts(_preimage) ⇒ Object
Returns hash256(72 zero bytes) in test mode. Consistent with passing all_prevouts = 72 zero bytes in tests, since extract_outpoint also returns 36 zero bytes.
301 302 303 |
# File 'lib/runar/builtins.rb', line 301 def extract_hash_prevouts(_preimage) hash256('00' * 72) end |
#extract_locktime(_preimage) ⇒ Object
– Mock Preimage Extraction ———————————————-
269 270 271 |
# File 'lib/runar/builtins.rb', line 269 def extract_locktime(_preimage) 0 end |
#extract_outpoint(_preimage) ⇒ Object
306 307 308 |
# File 'lib/runar/builtins.rb', line 306 def extract_outpoint(_preimage) '00' * 36 end |
#extract_output_hash(preimage) ⇒ Object
Returns the first 32 bytes of the preimage as a hex string. Tests set tx_preimage = hash256(expected_output_bytes) so the assertion hash256(outputs) == extract_output_hash(tx_preimage) passes. Falls back to 32 zero bytes when the preimage is shorter than 32 bytes.
277 278 279 280 281 282 283 284 |
# File 'lib/runar/builtins.rb', line 277 def extract_output_hash(preimage) preimage = preimage.to_s if preimage.length >= 64 # 32 bytes = 64 hex chars preimage[0, 64] else '00' * 32 end end |
#extract_sequence(_preimage) ⇒ Object
294 295 296 |
# File 'lib/runar/builtins.rb', line 294 def extract_sequence(_preimage) 0xFFFFFFFF end |
#extract_version(_preimage) ⇒ Object
290 291 292 |
# File 'lib/runar/builtins.rb', line 290 def extract_version(_preimage) 1 end |
#gcd(a, b) ⇒ Object
372 373 374 375 376 377 |
# File 'lib/runar/builtins.rb', line 372 def gcd(a, b) a = a.abs b = b.abs a, b = b, a % b while b != 0 a end |
#hash160(data) ⇒ Object
RIPEMD160(SHA256(data)). Input and output are hex-encoded strings.
256 257 258 259 |
# File 'lib/runar/builtins.rb', line 256 def hash160(data) sha_hex = sha256(data) ripemd160(sha_hex) end |
#hash256(data) ⇒ Object
SHA256(SHA256(data)). Input and output are hex-encoded strings.
262 263 264 265 |
# File 'lib/runar/builtins.rb', line 262 def hash256(data) sha_hex = sha256(data) sha256(sha_hex) end |
#left(data, length) ⇒ Object
Left-most bytes of a hex string.
418 419 420 |
# File 'lib/runar/builtins.rb', line 418 def left(data, length) data[0, length * 2] end |
#len(data) ⇒ Object
Byte length of a hex string (hex.length / 2).
402 403 404 |
# File 'lib/runar/builtins.rb', line 402 def len(data) data.length / 2 end |
#log2(n) ⇒ Object
384 385 386 387 388 |
# File 'lib/runar/builtins.rb', line 384 def log2(n) return 0 if n <= 0 n.bit_length - 1 end |
#mock_preimage ⇒ Object
496 497 498 |
# File 'lib/runar/builtins.rb', line 496 def mock_preimage '00' * 181 end |
#mock_pub_key ⇒ Object
Return ALICE’s real compressed secp256k1 public key (33 bytes, hex). This is a valid key that will pass check_sig verification when paired with mock_sig.
491 492 493 494 |
# File 'lib/runar/builtins.rb', line 491 def mock_pub_key require_relative 'test_keys' Runar::TestKeys::ALICE.pub_key end |
#mock_sig ⇒ Object
Return ALICE’s real ECDSA test signature (DER-encoded hex). This is a valid signature over TEST_MESSAGE_DIGEST that will pass check_sig verification when paired with mock_pub_key.
483 484 485 486 |
# File 'lib/runar/builtins.rb', line 483 def mock_sig require_relative 'test_keys' Runar::TestKeys::ALICE.test_sig end |
#mul_div(a, b, c) ⇒ Object
350 351 352 |
# File 'lib/runar/builtins.rb', line 350 def mul_div(a, b, c) (a * b) / c end |
#num2bin(v, length) ⇒ Object
Convert an integer to a little-endian sign-magnitude hex string of the specified byte length. This is Bitcoin Script’s number encoding. The sign bit is always in the MSB of the last byte.
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/runar/builtins.rb', line 435 def num2bin(v, length) return '00' * length if v == 0 negative = v < 0 val = v.abs result = [] while val > 0 result << (val & 0xFF) val >>= 8 end # Ensure the magnitude fits without colliding with the sign bit position if (result[-1] & 0x80) != 0 result << 0x00 end # Pad to requested length result << 0 while result.length < length # Truncate to requested length result = result[0, length] # Place sign bit in the MSB of the last byte result[-1] |= 0x80 if negative result.map { |b| format('%02x', b) }.join end |
#percent_of(amount, bps) ⇒ Object
354 355 356 |
# File 'lib/runar/builtins.rb', line 354 def percent_of(amount, bps) (amount * bps) / 10_000 end |
#pow(base, exp) ⇒ Object
346 347 348 |
# File 'lib/runar/builtins.rb', line 346 def pow(base, exp) base**exp end |
#reverse_bytes(data) ⇒ Object
Reverse the byte order of a hex string.
428 429 430 |
# File 'lib/runar/builtins.rb', line 428 def reverse_bytes(data) [data].pack('H*').reverse.unpack1('H*') end |
#right(data, length) ⇒ Object
Right-most bytes of a hex string.
423 424 425 |
# File 'lib/runar/builtins.rb', line 423 def right(data, length) data[-(length * 2)..] end |
#ripemd160(data) ⇒ Object
RIPEMD-160 hash. Input and output are hex-encoded strings.
250 251 252 253 |
# File 'lib/runar/builtins.rb', line 250 def ripemd160(data) raw = [data].pack('H*') Digest::RMD160.hexdigest(raw) end |
#safediv(a, b) ⇒ Object
– Math Utilities ——————————————————–
312 313 314 315 316 317 318 319 320 321 |
# File 'lib/runar/builtins.rb', line 312 def safediv(a, b) return 0 if b == 0 # Bitcoin Script truncates toward zero, unlike Ruby's floor division. if (a < 0) != (b < 0) && a % b != 0 -(a.abs / b.abs) else a / b end end |
#safemod(a, b) ⇒ Object
323 324 325 326 327 328 329 330 |
# File 'lib/runar/builtins.rb', line 323 def safemod(a, b) return 0 if b == 0 r = a % b # Ensure sign matches dividend (Bitcoin Script behavior) r -= b if r != 0 && (a < 0) != (r < 0) r end |
#sha256(data) ⇒ Object
SHA-256 hash. Input and output are hex-encoded strings.
244 245 246 247 |
# File 'lib/runar/builtins.rb', line 244 def sha256(data) raw = [data].pack('H*') Digest::SHA256.hexdigest(raw) end |
#sha256_compress(state, block) ⇒ Object
SHA-256 single-block compression function (FIPS 180-4 Section 6.2.2).
Performs one round of SHA-256 block compression: message schedule expansion (W), 64 compression rounds with Sigma/Ch/Maj functions and the K constants, then addition back to the initial state.
Both state and block are hex-encoded strings. State is 32 bytes (64 hex chars); block is 64 bytes (128 hex chars). Returns a 32-byte hex string.
Use the SHA-256 IV as state for the first block:
6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19
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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/runar/builtins.rb', line 135 def sha256_compress(state, block) state_bytes = [state].pack('H*') block_bytes = [block].pack('H*') raise ArgumentError, "state must be 32 bytes, got #{state_bytes.bytesize}" unless state_bytes.bytesize == 32 raise ArgumentError, "block must be 64 bytes, got #{block_bytes.bytesize}" unless block_bytes.bytesize == 64 # Parse state as 8 big-endian uint32 words h = state_bytes.unpack('N8') # Parse block as 16 big-endian uint32 words and expand to message schedule w = block_bytes.unpack('N16') (16..63).each do |t| s0 = (_sha256_rotr(w[t - 15], 7) ^ _sha256_rotr(w[t - 15], 18) ^ (w[t - 15] >> 3)) & 0xFFFFFFFF s1 = (_sha256_rotr(w[t - 2], 17) ^ _sha256_rotr(w[t - 2], 19) ^ (w[t - 2] >> 10)) & 0xFFFFFFFF w << ((s1 + w[t - 7] + s0 + w[t - 16]) & 0xFFFFFFFF) end # Initialize working variables a, b, c, d, e, f, g, hh = h # 64 compression rounds 64.times do |t| s1 = (_sha256_rotr(e, 6) ^ _sha256_rotr(e, 11) ^ _sha256_rotr(e, 25)) & 0xFFFFFFFF ch = ((e & f) ^ (~e & g)) & 0xFFFFFFFF temp1 = (hh + s1 + ch + SHA256_K[t] + w[t]) & 0xFFFFFFFF s0 = (_sha256_rotr(a, 2) ^ _sha256_rotr(a, 13) ^ _sha256_rotr(a, 22)) & 0xFFFFFFFF maj = ((a & b) ^ (a & c) ^ (b & c)) & 0xFFFFFFFF temp2 = (s0 + maj) & 0xFFFFFFFF hh = g g = f f = e e = (d + temp1) & 0xFFFFFFFF d = c c = b b = a a = (temp1 + temp2) & 0xFFFFFFFF end # Add compressed chunk back to state new_state = [ (h[0] + a) & 0xFFFFFFFF, (h[1] + b) & 0xFFFFFFFF, (h[2] + c) & 0xFFFFFFFF, (h[3] + d) & 0xFFFFFFFF, (h[4] + e) & 0xFFFFFFFF, (h[5] + f) & 0xFFFFFFFF, (h[6] + g) & 0xFFFFFFFF, (h[7] + hh) & 0xFFFFFFFF ] new_state.pack('N8').unpack1('H*') end |
#sha256_finalize(state, remaining, msg_bit_len) ⇒ Object
SHA-256 finalization with FIPS 180-4 padding.
Applies SHA-256 padding (append 0x80 byte, zero-pad, append 8-byte big-endian bit length) and runs the final 1-2 compression rounds:
-
Single-block path (remaining <= 55 bytes): pads to one 64-byte block and compresses once.
-
Two-block path (56-119 bytes): pads to two 64-byte blocks and compresses twice.
state is a 32-byte hex string (SHA-256 IV for first call, or output of a prior sha256_compress for multi-block). remaining is a hex string of unprocessed trailing message bytes (0-119 bytes). msg_bit_len is the total message length in bits across all blocks. Returns a 32-byte hex string.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/runar/builtins.rb', line 203 def sha256_finalize(state, remaining, msg_bit_len) remaining_bytes = [remaining].pack('H*') # Append the 0x80 padding byte padded = remaining_bytes + "\x80".b if padded.bytesize + 8 <= 64 # Single-block path: pad to 56 bytes, then append 8-byte BE bit length padded = padded.ljust(56, "\x00".b) padded += [msg_bit_len].pack('Q>') sha256_compress(state, padded.unpack1('H*')) else # Two-block path: pad to 120 bytes, then append 8-byte BE bit length padded = padded.ljust(120, "\x00".b) padded += [msg_bit_len].pack('Q>') intermediate = sha256_compress(state, padded[0, 64].unpack1('H*')) sha256_compress(intermediate, padded[64, 64].unpack1('H*')) end end |
#sign(n) ⇒ Object
339 340 341 342 343 344 |
# File 'lib/runar/builtins.rb', line 339 def sign(n) return 1 if n > 0 return -1 if n < 0 0 end |
#sqrt(n) ⇒ Object
Integer square root using Newton’s method.
359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/runar/builtins.rb', line 359 def sqrt(n) raise ArgumentError, 'sqrt of negative number' if n < 0 return 0 if n == 0 x = n y = (x + 1) / 2 while y < x x = y y = (x + n / x) / 2 end x end |
#substr(data, start, length) ⇒ Object
Extract a substring of bytes from a hex string. start and length are in bytes, not hex characters.
413 414 415 |
# File 'lib/runar/builtins.rb', line 413 def substr(data, start, length) data[start * 2, length * 2] end |
#verify_rabin_sig(msg, sig, padding, pk) ⇒ Object
Verify a Rabin signature. All parameters are hex-encoded strings. sig, padding, and pk are interpreted as unsigned little-endian integers.
63 64 65 |
# File 'lib/runar/builtins.rb', line 63 def verify_rabin_sig(msg, sig, padding, pk) Runar::RabinSig.rabin_verify(msg, sig, padding, pk) end |
#verify_slh_dsa_sha2_128f(_msg, _sig, _pubkey) ⇒ Object
75 76 77 |
# File 'lib/runar/builtins.rb', line 75 def verify_slh_dsa_sha2_128f(_msg, _sig, _pubkey) true end |
#verify_slh_dsa_sha2_128s(_msg, _sig, _pubkey) ⇒ Object
71 72 73 |
# File 'lib/runar/builtins.rb', line 71 def verify_slh_dsa_sha2_128s(_msg, _sig, _pubkey) true end |
#verify_slh_dsa_sha2_192f(_msg, _sig, _pubkey) ⇒ Object
83 84 85 |
# File 'lib/runar/builtins.rb', line 83 def verify_slh_dsa_sha2_192f(_msg, _sig, _pubkey) true end |
#verify_slh_dsa_sha2_192s(_msg, _sig, _pubkey) ⇒ Object
79 80 81 |
# File 'lib/runar/builtins.rb', line 79 def verify_slh_dsa_sha2_192s(_msg, _sig, _pubkey) true end |
#verify_slh_dsa_sha2_256f(_msg, _sig, _pubkey) ⇒ Object
91 92 93 |
# File 'lib/runar/builtins.rb', line 91 def verify_slh_dsa_sha2_256f(_msg, _sig, _pubkey) true end |
#verify_slh_dsa_sha2_256s(_msg, _sig, _pubkey) ⇒ Object
87 88 89 |
# File 'lib/runar/builtins.rb', line 87 def verify_slh_dsa_sha2_256s(_msg, _sig, _pubkey) true end |
#verify_wots(_msg, _sig, _pubkey) ⇒ Object
67 68 69 |
# File 'lib/runar/builtins.rb', line 67 def verify_wots(_msg, _sig, _pubkey) true end |
#within(x, lo, hi) ⇒ Object
394 395 396 |
# File 'lib/runar/builtins.rb', line 394 def within(x, lo, hi) lo <= x && x < hi end |