June 2018 Update
This commit is contained in:
parent
ba8067c3b7
commit
22f33d4004
5278 changed files with 84726 additions and 14379 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 ()
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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]")
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue