Generate random number

Create pseudo-random numbers using block hashes for randomness in smart contracts


(define-read-only (generate-random (block-height uint))
(let (
;; Get block header hash
(block-hash (unwrap! (get-stacks-block-info? id-header-hash block-height) (err u1001)))
;; Take a slice of the hash for randomness
(hash-slice (unwrap-panic (slice? block-hash u16 u32)))
;; Convert to uint
(random-value (buff-to-uint-be (unwrap-panic (as-max-len? hash-slice u16))))
)
(ok random-value)
)
)
;; Generate random number in range
(define-read-only (random-in-range (block-height uint) (min uint) (max uint))
(let (
(random (try! (generate-random block-height)))
(range (- max min))
)
(ok (+ min (mod random (+ u1 range))))
)
)
;; Example: Random between 1-100
(random-in-range block-height u1 u100)

Use cases

  • Lottery and gaming contracts
  • Random NFT trait generation
  • Fair distribution mechanisms
  • Random selection from lists

Key concepts

Blockchain randomness is deterministic but unpredictable:

  • Block hashes: Use historical block data as entropy source
  • Future blocks: Cannot predict future block hashes
  • Commitment schemes: Combine with commit-reveal for fairness

Advanced random generation

;; Random with multiple sources of entropy
(define-read-only (enhanced-random (block-height uint) (user-seed uint))
(let (
;; Combine block hash with user seed
(block-hash (unwrap! (get-stacks-block-info? id-header-hash block-height) (err u1001)))
(combined (sha256 (concat block-hash (unwrap-panic (to-consensus-buff? user-seed)))))
;; Use multiple slices for better distribution
(slice1 (buff-to-uint-be (unwrap-panic (slice? combined u0 u8))))
(slice2 (buff-to-uint-be (unwrap-panic (slice? combined u8 u16))))
)
(ok (+ slice1 slice2))
)
)
;; Shuffle a list using randomness
(define-read-only (shuffle-list (items (list 10 uint)) (block-height uint))
(let (
(seed (default-to u0 (generate-random block-height)))
)
(fold shuffle-item items { result: (list), seed: seed })
)
)

Fair lottery implementation

(define-map lottery-entries principal uint)
(define-data-var lottery-block uint u0)
(define-data-var total-tickets uint u0)
;; Enter lottery
(define-public (enter-lottery (tickets uint))
(let (
(current-tickets (default-to u0 (map-get? lottery-entries tx-sender)))
)
(map-set lottery-entries tx-sender (+ current-tickets tickets))
(var-set total-tickets (+ (var-get total-tickets) tickets))
(ok true)
)
)
;; Set lottery drawing block (must be future block)
(define-public (set-lottery-block (block uint))
(begin
(asserts! (> block block-height) (err u1))
(var-set lottery-block block)
(ok true)
)
)
;; Draw winner after lottery block is mined
(define-read-only (draw-winner)
(let (
(draw-block (var-get lottery-block))
(total (var-get total-tickets))
(random (try! (random-in-range draw-block u0 total)))
)
;; In practice, iterate through entries to find winner
(ok random)
)
)

Security considerations

;; Commit-reveal pattern for user-influenced randomness
(define-map commitments principal (buff 32))
(define-map reveals principal uint)
;; Commit phase
(define-public (commit-random (commitment (buff 32)))
(begin
(map-set commitments tx-sender commitment)
(ok true)
)
)
;; Reveal phase (after target block)
(define-public (reveal-random (secret uint) (target-block uint))
(let (
(commitment (unwrap! (map-get? commitments tx-sender) (err u1)))
(calculated (sha256 (unwrap-panic (to-consensus-buff? secret))))
)
(asserts! (is-eq commitment calculated) (err u2))
(asserts! (>= block-height target-block) (err u3))
(map-set reveals tx-sender secret)
;; Generate final random using block + all reveals
(ok true)
)
)
Randomness warning

Block hashes provide economic security but not cryptographic randomness. For high-value applications, use commit-reveal schemes or external randomness beacons with proper verification.