Convert BTC to STX address

Convert Bitcoin addresses to their corresponding Stacks addresses using base58 decoding in Clarity


(define-read-only (btc-to-stx (input (string-ascii 60)))
(let (
;; Decode base58 string to numbers
(b58-numbers (map unwrap-uint (filter is-some-uint (map b58-to-uint input))))
;; Validate all characters are valid base58
(t1 (asserts! (>= (len b58-numbers) (len input)) ERR_INVALID_CHAR))
;; Count leading '1's (zeros in base58)
(leading-ones-count (default-to (len input) (index-of? (map is-zero b58-numbers) false)))
;; Convert to bytes
(decoded (concat (fold decode-outer to-decode LST) leading-zeros))
(decoded-hex (fold to-hex-rev decoded 0x))
;; Verify checksum
(actual-checksum (unwrap-panic (slice? (sha256 (sha256 (unwrap-panic (slice? decoded-hex u0 (- decoded-hex-len u4))))) u0 u4)))
(expected-checksum (unwrap-panic (slice? decoded-hex (- decoded-hex-len u4) decoded-hex-len)))
(t3 (asserts! (is-eq actual-checksum expected-checksum) ERR_BAD_CHECKSUM))
;; Extract version and construct principal
(version (unwrap-panic (element-at? STX_VER (unwrap! (index-of? BTC_VER (unwrap-panic (element-at? decoded-hex u0))) ERR_INVALID_VERSION))))
)
(principal-construct? version (unwrap-panic (as-max-len? (unwrap-panic (slice? decoded-hex u1 (- decoded-hex-len u4))) u20)))
)
)
;; Example usage
(btc-to-stx "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") ;; Returns Stacks address

Use cases

  • Cross-chain address mapping for Bitcoin-Stacks bridges
  • Verifying ownership across both chains
  • Converting legacy Bitcoin addresses to Stacks format
  • Building cross-chain authentication systems

Key concepts

The conversion process involves:

  1. 1Base58 decoding: Bitcoin addresses use base58 encoding
  2. 2Checksum verification: Last 4 bytes are a double SHA-256 checksum
  3. 3Version mapping: Bitcoin version bytes map to Stacks version bytes
  4. 4Principal construction: Build Stacks principal from decoded data

Complete implementation

;; Constants for base58 alphabet and version mappings
(define-constant BASE58_CHARS "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
(define-constant STX_VER 0x16141a15)
(define-constant BTC_VER 0x00056fc4)
(define-constant ALL_HEX 0x000102...FF) ;; All hex values 0-255
;; Error constants
(define-constant ERR_INVALID_CHAR (err u1001))
(define-constant ERR_TOO_SHORT (err u1002))
(define-constant ERR_BAD_CHECKSUM (err u1003))
(define-constant ERR_INVALID_VERSION (err u1004))
;; Helper functions
(define-read-only (b58-to-uint (x (string-ascii 1)))
(index-of? BASE58_CHARS x))
(define-read-only (is-some-uint (x (optional uint)))
(is-some x))
(define-read-only (unwrap-uint (x (optional uint)))
(unwrap-panic x))
;; Base conversion logic
(define-read-only (decode-outer (x uint) (out (list 33 uint)))
(let (
(new-out (fold update-out out (list x)))
(carry-to-push (fold carry-push 0x0000 (list (unwrap-panic (element-at? new-out u0)))))
)
(concat
(default-to (list) (slice? new-out u1 (len new-out)))
(default-to (list) (slice? carry-to-push u1 (len carry-to-push)))
)
)
)

How it works

The algorithm:

  1. 1Validate input: Ensure all characters are valid base58
  2. 2Count leading 1s: Bitcoin addresses use '1' as padding
  3. 3Decode base58: Convert to base256 (bytes)
  4. 4Extract components:
    • Version byte (first byte)
    • Hash160 (next 20 bytes)
    • Checksum (last 4 bytes)
  5. 5Verify checksum: Double SHA-256 of version + hash160
  6. 6Map version: Convert Bitcoin version to Stacks version
  7. 7Construct principal: Build Stacks address

Supported address types

Bitcoin PrefixTypeStacks Version
1P2PKH (mainnet)22 (P)
3P2SH (mainnet)20 (M)
bc1SegwitNot supported
Limitation

This implementation only supports legacy Bitcoin addresses (P2PKH and P2SH). Native Segwit (bech32) addresses require a different encoding scheme.

Testing addresses

;; Test with known Bitcoin addresses
(btc-to-stx "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") ;; Satoshi's address
(btc-to-stx "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") ;; P2SH address
(btc-to-stx "1CounterpartyXXXXXXXXXXXXXXXUWLpVr") ;; Counterparty burn