June 2018 Update

This commit is contained in:
Ingy döt Net 2018-06-22 20:57:24 +00:00
parent ba8067c3b7
commit 22f33d4004
5278 changed files with 84726 additions and 14379 deletions

View file

@ -1,26 +1,36 @@
{{omit from|Brlcad}}
{{omit from|GUISS}}
;Task:
Write a program that takes a [[wp:bitcoin|bitcoin address]] as argument,
and checks whether or not this address is valid.
A bitcoin address uses a base58 encoding, which uses an alphabet of the characters 0 .. 9, A ..Z, a .. z, but without the four characters 0, O, I and l.
A bitcoin address uses a base58 encoding, which uses an alphabet of the characters 0 .. 9, A ..Z, a .. z, but without the four characters:
:::*   0   zero
:::*   O   uppercase oh
:::*   I   uppercase eye
:::*   l   lowercase ell
With this encoding, a bitcoin address encodes 25 bytes:
* the first byte is the version number, which will be zero for this task ;
* the next twenty bytes are a [[RIPEMD-160]] digest, but you don't have to know that for this task: you can consider them a pure arbitrary data ;
* the last four bytes are a checksum check. They are the first four bytes of a double [[SHA-256]] digest of the previous 21 bytes.
To check the bitcoin address, you must read the first twenty-one bytes, compute the checksum, and check that it corresponds to the last four bytes.
The program can either return a boolean value or throw an exception when not valid.
You can use a digest library for [[SHA-256]].
Here is an example of a bitcoin address:
1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i
;Example of a bitcoin address:
<big>
1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i
</big>
It does not belong to anyone.
It is part of the test suite of the bitcoin software.
You can change a few characters in this string and check that it will fail the test.
It doesn't belong to anyone and is part of the test suite of the bitcoin software.
<br>You can change a few characters in this string and check that it'll fail the test.
<br><br>

View file

@ -0,0 +1,64 @@
uses
DCPsha256;
type
TByteArray = array of Byte;
function HashSHA256(const Input: TByteArray): TByteArray;
var
Hasher: TDCP_sha256;
begin
Hasher := TDCP_sha256.Create(nil);
try
Hasher.Init;
Hasher.Update(Input[0], Length(Input));
SetLength(Result, Hasher.HashSize div 8);
Hasher.Final(Result[0]);
finally
Hasher.Free;
end;
end;
function DecodeBase58(const Input: string): TByteArray;
const
Size = 25;
Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
var
C: Char;
I, J: Integer;
begin
SetLength(Result, Size);
for C in Input do
begin
I := Pos(C, Alphabet) - 1;
if I = -1 then
raise Exception.CreateFmt('Invalid character found: %s', [C]);
for J := High(Result) downto 0 do
begin
I := I + (58 * Result[J]);
Result[J] := I mod 256;
I := I div 256;
end;
if I <> 0 then
raise Exception.Create('Address too long');
end;
end;
procedure ValidateBitcoinAddress(const Address: string);
var
Hashed: TByteArray;
Decoded: TByteArray;
begin
if (Length(Address) < 26) or (Length(Address) > 35) then
raise Exception.Create('Wrong length');
Decoded := DecodeBase58(Address);
Hashed := HashSHA256(HashSHA256(Copy(Decoded, 0, 21)));
if not CompareMem(@Decoded[21], @Hashed[0], 4) then
raise Exception.Create('Bad digest');
end;

View file

@ -1,45 +1,50 @@
import Data.List (unfoldr, elemIndex)
import Data.Binary (Word8)
import Crypto.Hash.SHA256 (hash)
import Data.ByteString (unpack, pack)
import Control.Monad (when)
import Data.List (elemIndex)
import Data.Monoid ((<>))
import qualified Data.ByteString as BS
import Data.ByteString (ByteString)
import Crypto.Hash.SHA256 (hash) -- from package cryptohash
-- Convert from base58 encoded value to Integer
decode58 :: String -> Maybe Integer
decode58 = foldl (\v d -> (+) <$> ((58*) <$> v) <*> (fromIntegral <$> elemIndex d c58 )) $ Just 0
where c58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
decode58 = fmap combine . traverse parseDigit
where
combine = foldl (\acc digit -> 58 * acc + digit) 0 -- should be foldl', but this trips up the highlighting
parseDigit char = toInteger <$> elemIndex char c58
c58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
-- Convert from base58 encoded value to list of bytes
toBytes :: Integer -> [Word8]
toBytes x = reverse $ unfoldr (\b -> if b == 0 then Nothing else Just (fromIntegral $ b `mod` 256, b `div` 256)) x
-- Convert from base58 encoded value to bytes
toBytes :: Integer -> ByteString
toBytes = BS.reverse . BS.pack . map (fromIntegral . (`mod` 256)) . takeWhile (> 0) . iterate (`div` 256)
-- Check if the hash of the first 21 (padded) bytes matches the last 4 bytes
checksumValid :: ByteString -> Bool
checksumValid address =
let (value, checksum) = BS.splitAt 21 $ leftPad address
in and $ BS.zipWith (==) checksum $ hash $ hash $ value
where
leftPad bs = BS.replicate (25 - BS.length bs) 0 <> bs
-- utility
withError :: e -> Maybe a -> Either e a
withError e = maybe (Left e) Right
-- Check validity of base58 encoded bitcoin address.
-- Result is either an error string (Left) or a validity bool (Right).
validityCheck :: String -> Either String Bool
validityCheck encodedAddress =
let d58 = decode58 encodedAddress
in case d58 of
Nothing -> Left "Invalid base 58 encoding"
Just ev ->
let address = toBytes ev
addressLength = length address
in if addressLength > 25
then Left "Address length exceeds 25 bytes"
else
if addressLength < 4
then Left "Address length less than 4 bytes"
else
let (bs,cs) = splitAt 21 $ replicate (25 - addressLength) 0 ++ address
in Right $ all (uncurry (==)) (zip cs $ unpack $ hash $ hash $ pack bs)
-- Result is either an error string (Left) or unit (Right ()).
validityCheck :: String -> Either String ()
validityCheck encoded = do
num <- withError "Invalid base 58 encoding" $ decode58 encoded
let address = toBytes num
when (BS.length address > 25) $ Left "Address length exceeds 25 bytes"
when (BS.length address < 4) $ Left "Address length less than 4 bytes"
when (not $ checksumValid address) $ Left "Invalid checksum"
-- Run one validity check and display results.
validate :: String -> IO ()
validate encodedAddress =
let vc = validityCheck encodedAddress
in case vc of
Left err ->
putStrLn $ show encodedAddress ++ " -> " ++ show err
Right validity ->
putStrLn $ show encodedAddress ++ " -> " ++ if validity then "Valid" else "Invalid"
validate encodedAddress = do
let result = either show (const "Valid") $ validityCheck encodedAddress
putStrLn $ show encodedAddress ++ " -> " ++ result
-- Run some validity check tests.
main :: IO ()

View file

@ -1,3 +1,4 @@
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@ -9,7 +10,7 @@ public class BitcoinAddressValidator {
public static boolean validateBitcoinAddress(String addr) {
if (addr.length() < 26 || addr.length() > 35)
return false;
byte[] decoded = decodeBase58(addr, 25);
byte[] decoded = decodeBase58To25Bytes(addr);
if (decoded == null)
return false;
@ -19,22 +20,19 @@ public class BitcoinAddressValidator {
return Arrays.equals(Arrays.copyOfRange(hash2, 0, 4), Arrays.copyOfRange(decoded, 21, 25));
}
private static byte[] decodeBase58(String input, int len) {
byte[] output = new byte[len];
private static byte[] decodeBase58To25Bytes(String input) {
BigInteger num = BigInteger.ZERO;
for (char t : input.toCharArray()) {
int p = ALPHABET.indexOf(t);
if (p == -1)
return null;
for (int j = len - 1; j >= 0; j--) {
p += 58 * (output[j] & 0xFF);
output[j] = (byte) (p % 256);
p /= 256;
}
if (p != 0)
return null;
num = num.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(p));
}
return output;
byte[] result = new byte[25];
byte[] numBytes = num.toByteArray();
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length);
return result;
}
private static byte[] sha256(byte[] data) {

View file

@ -0,0 +1,38 @@
using SHA
bytes(n::Integer, l::Int) = collect(UInt8, (n >> 8i) & 0xFF for i in l-1:-1:0)
function decodebase58(bc::String, l::Int)
digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
num = big(0)
for c in bc
num = num * 58 + search(digits, c) - 1
end
return bytes(num, l)
end
function checkbcaddress(addr::String)
if !(25 ≤ length(addr) ≤ 35) return false end
bcbytes = decodebase58(addr, 25)
sha = sha256(sha256(bcbytes[1:end-4]))
return bcbytes[end-3:end] == sha[1:4]
end
const addresses = Dict(
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" => true,
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j" => false,
"1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9" => true,
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X" => true,
"1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" => false,
"1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" => false,
"BZbvjr" => false,
"i55j" => false,
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62!" => false,
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz" => false,
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz" => false,
"1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9" => false,
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I" => false)
for (addr, corr) in addresses
println("Address: $addr\nExpected: $corr\tChecked: ", checkbcaddress(addr))
end

View file

@ -8,8 +8,11 @@ def decode_base58(bc, length):
n = n * 58 + digits58.index(char)
return n.to_bytes(length, 'big')
def check_bc(bc):
bcbytes = decode_base58(bc, 25)
return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]
try:
bcbytes = decode_base58(bc, 25)
return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]
except Exception:
return False
print(check_bc('1AGNa15ZQXAZUgFiqJ3i7Z2DPU2J6hW62i'))
print(check_bc("17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j"))

View file

@ -0,0 +1,67 @@
import java.security.MessageDigest
import java.util.Arrays.copyOfRange
import scala.annotation.tailrec
import scala.math.BigInt
object BitcoinAddressValidator extends App {
private def bitcoinTestHarness(address: String, expected: Boolean): Unit =
assert(validateBitcoinAddress(address) == expected, s"Expected $expected for $address%s, but got ${!expected}.")
private def validateBitcoinAddress(addr: String): Boolean = {
def sha256(data: Array[Byte]) = {
val md: MessageDigest = MessageDigest.getInstance("SHA-256")
md.update(data)
md.digest
}
def decodeBase58To25Bytes(input: String): Option[Array[Byte]] = {
def ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
@tailrec
def loop(s: String, accu: BigInt): BigInt = {
if (s.isEmpty) accu
else {
val p = ALPHABET.indexOf(s.head)
if (p >= 0) loop(s.tail, accu * 58 + p)
else -1
}
}
val num = loop(input, 0)
if (num >= 0) {
val (result, numBytes) = (new Array[Byte](25), num.toByteArray)
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length)
Some(result)
}
else None
}
if (27 to 34 contains addr.length) {
val decoded = decodeBase58To25Bytes(addr)
if (decoded.isEmpty) false
else {
val hash1 = sha256(copyOfRange(decoded.get, 0, 21))
copyOfRange(sha256(hash1), 0, 4)
.sameElements(copyOfRange(decoded.get, 21, 25))
}
} else false
} // validateBitcoinAddress
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", true)
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j", false)
bitcoinTestHarness("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9", true)
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X", false)
bitcoinTestHarness("1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", false)
bitcoinTestHarness("1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", false)
bitcoinTestHarness("BZbvjr", false)
bitcoinTestHarness("i55j", false)
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62!", false)
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz", false)
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz", false)
bitcoinTestHarness("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9", false)
bitcoinTestHarness("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I", false)
println(s"Successfully completed without errors. [total ${scala.compat.Platform.currentTime - executionStart}ms]")
}