How to resolve the algorithm Bitcoin/public point to address step by step in the Racket programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm Bitcoin/public point to address step by step in the Racket programming language

Table of Contents

Problem Statement

Bitcoin uses a specific encoding format to encode the digest of an elliptic curve public point into a short ASCII string. The purpose of this task is to perform such a conversion. The encoding steps are: The base-58 encoding is based on an alphabet of alphanumeric characters (numbers, upper case and lower case, in that order) but without the four characters 0, O, l and I. Here is an example public point: The corresponding address should be: 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM Nb. The leading '1' is not significant as 1 is zero in base-58. It is however often added to the bitcoin address for various reasons. There can actually be several of them. You can ignore this and output an address without the leading 1. Extra credit: add a verification procedure about the public point, making sure it belongs to the secp256k1 elliptic curve

Let's start with the solution:

Step by Step solution about How to resolve the algorithm Bitcoin/public point to address step by step in the Racket programming language

Source code in the racket programming language

#lang racket/base
(module sha256 racket/base
  ;; define a quick SH256 FFI interface, similar to the Racket's default
  ;; SHA1 interface (from [[SHA-256#Racket]])
  (provide sha256)
  (require ffi/unsafe ffi/unsafe/define openssl/libcrypto)
  (define-ffi-definer defcrypto libcrypto)
  (defcrypto SHA256_Init   (_fun _pointer -> _int))
  (defcrypto SHA256_Update (_fun _pointer _pointer _long -> _int))
  (defcrypto SHA256_Final  (_fun _pointer _pointer -> _int))
  (define (sha256 bytes)
    (define ctx (malloc 128))
    (define result (make-bytes 32))
    (SHA256_Init ctx)
    (SHA256_Update ctx bytes (bytes-length bytes))
    (SHA256_Final result ctx)
    ;; (bytes->hex-string result) -- not needed, we want bytes
    result))

(require
  ;; On windows I needed to "raco planet install soegaard digetst.plt 1 2"
  (only-in (planet soegaard/digest:1:2/digest) ripemd160-bytes)
  (submod "." sha256))

;; Quick utility
(define << arithmetic-shift) ; a bit shorter

;; From: https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
;; Using Bitcoin's stage numbers:

;; 1 - Take the corresponding public key generated with it
;; (65 bytes, 1 byte 0x04, 32 bytes corresponding to X coordinate,
;;  32 bytes corresponding to Y coordinate) 
(define (stage-1 X Y)
  (define (integer->bytes! i B)
    (define l (bytes-length B))
    (for ((b (in-range 0 l))) (bytes-set! B (- l b 1) (bitwise-bit-field i (* b 8) (* (+ b 1) 8)))) B)
  (integer->bytes! (+ (<< 4 (* 32 8 2)) (<< X (* 32 8)) Y) (make-bytes 65)))

;; 2 - Perform SHA-256 hashing on the public key 
(define stage-2 sha256)

;; 3 - Perform RIPEMD-160 hashing on the result of SHA-256 
(define stage-3 ripemd160-bytes)

;; 4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network) 
(define (stage-4 s3)
  (bytes-append #"\0" s3))

;; 5 - Perform SHA-256 hash on the extended RIPEMD-160 result 
;; 6 - Perform SHA-256 hash on the result of the previous SHA-256 hash 
(define (stage-5+6 s4)
  (values s4 (sha256 (sha256 s4))))

;; 7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum
(define (stage-7 s4 s6)
  (values s4 (subbytes s6 0 4)))

;; 8 - Add the 4 checksum bytes from stage 7 at the end of extended RIPEMD-160 hash from stage 4.
;;     This is the 25-byte binary Bitcoin Address. 
(define (stage-8 s4 s7)
  (bytes-append s4 s7))

;; 9 - Convert the result from a byte string into a base58 string using Base58Check encoding.
;;     This is the most commonly used Bitcoin Address format 
(define stage-9 (base58-encode 33))

(define ((base58-encode l) B)
  (define b58 #"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
  (define rv (make-bytes l (char->integer #\1))) ; already padded out with 1's
  (define b-int (for/fold ((i 0)) ((b (in-bytes B))) (+ (<< i 8) b)))
  (let loop ((b b-int) (l l))
    (if (zero? b) rv
        (let-values (((q r) (quotient/remainder b 58)))
          (define l- (- l 1))
          (bytes-set! rv l- (bytes-ref b58 r))
          (loop q l-)))))

;; Takes two (rather large) ints for X and Y, returns base-58 PAP.
(define public-address-point
  (compose stage-9 stage-8 stage-7 stage-5+6 stage-4 stage-3 stage-2 stage-1))

(public-address-point
 #x50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352
 #x2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(module+ test
  (require tests/eli-tester (only-in openssl/sha1 bytes->hex-string))
  (define bytes->HEX-STRING (compose string-upcase bytes->hex-string))
  (test
   ((base58-encode 33)
    (bytes-append #"\x00\x01\x09\x66\x77\x60\x06\x95\x3D\x55\x67\x43"
                  #"\x9E\x5E\x39\xF8\x6A\x0D\x27\x3B\xEE\xD6\x19\x67\xF6"))
   =>
   #"16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM")
  
  (define-values (test-X test-Y)
    (values #x50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352
            #x2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6))
  (define s1 (stage-1 test-X test-Y))
  (define s2 (stage-2 s1))
  (define s3 (stage-3 s2))
  (define s4 (stage-4 s3))
  (define-values (s4_1 s6) (stage-5+6 s4))
  (define-values (s4_2 s7) (stage-7 s4 s6))
  (define s8 (stage-8 s4 s7))
  (define s9 (stage-9 s8))
  
  (test
   (bytes->HEX-STRING s1)
   => (string-append "0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453"
                     "A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6")
   (bytes->HEX-STRING s2) => "600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408"
   (bytes->HEX-STRING s3) => "010966776006953D5567439E5E39F86A0D273BEE"
   (bytes->HEX-STRING s4) => "00010966776006953D5567439E5E39F86A0D273BEE"
   (bytes->HEX-STRING s6) => "D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30"
   (bytes->HEX-STRING s7) => "D61967F6"
   (bytes->HEX-STRING s8) => "00010966776006953D5567439E5E39F86A0D273BEED61967F6"
   s9 => #"16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"))


  

You may also check:How to resolve the algorithm Short-circuit evaluation step by step in the Raku programming language
You may also check:How to resolve the algorithm Collections step by step in the MS SmallBasic programming language
You may also check:How to resolve the algorithm Associative array/Iteration step by step in the AutoHotkey programming language
You may also check:How to resolve the algorithm P-Adic numbers, basic step by step in the Haskell programming language
You may also check:How to resolve the algorithm AKS test for primes step by step in the Ruby programming language