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:
- 1Base58 decoding: Bitcoin addresses use base58 encoding
- 2Checksum verification: Last 4 bytes are a double SHA-256 checksum
- 3Version mapping: Bitcoin version bytes map to Stacks version bytes
- 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:
- 1Validate input: Ensure all characters are valid base58
- 2Count leading 1s: Bitcoin addresses use '1' as padding
- 3Decode base58: Convert to base256 (bytes)
- 4Extract components:
- Version byte (first byte)
- Hash160 (next 20 bytes)
- Checksum (last 4 bytes)
- 5Verify checksum: Double SHA-256 of version + hash160
- 6Map version: Convert Bitcoin version to Stacks version
- 7Construct principal: Build Stacks address
Supported address types
Bitcoin Prefix | Type | Stacks Version |
---|---|---|
1 | P2PKH (mainnet) | 22 (P) |
3 | P2SH (mainnet) | 20 (M) |
bc1 | Segwit | Not 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