Another update from ingydotnet^djgoku
This commit is contained in:
parent
91df62d461
commit
948b86eafa
7604 changed files with 108452 additions and 22726 deletions
|
|
@ -24,5 +24,3 @@ Here is an example of a bitcoin address:
|
|||
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.
|
||||
|
||||
''extra credit'': allow your code to deal with [http://bitcoin.stackexchange.com/questions/3059/what-is-a-compressed-bitcoin-key compressed keys]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import Data.List (unfoldr, elemIndex)
|
||||
import Data.Binary (Word8)
|
||||
import Crypto.Hash.SHA256 (hash)
|
||||
import Data.ByteString (unpack, pack)
|
||||
|
||||
-- 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"
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 > 24
|
||||
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)
|
||||
|
||||
-- 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"
|
||||
|
||||
-- Run some validity check tests.
|
||||
main :: IO ()
|
||||
main = do
|
||||
validate "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- VALID
|
||||
validate "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9" -- VALID
|
||||
validate "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X" -- checksum changed, original data.
|
||||
validate "1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- data changed, original checksum.
|
||||
validate "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- invalid chars
|
||||
validate "1ANa55215ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- too long
|
||||
validate "i55j" -- too short
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
private final static String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
public static void main(String[] args) {
|
||||
Assert("Test 01", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"), true);
|
||||
Assert("Test 02", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j"), false);
|
||||
Assert("Test 03", ValidateBitcoinAddress("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9"), true);
|
||||
Assert("Test 04", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X"), false);
|
||||
Assert("Test 05", ValidateBitcoinAddress("1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"), false);
|
||||
Assert("Test 06", ValidateBitcoinAddress("1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"), false);
|
||||
Assert("Test 07", ValidateBitcoinAddress("BZbvjr"), false);
|
||||
Assert("Test 08", ValidateBitcoinAddress("i55j"), false);
|
||||
Assert("Test 09", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62!"), false);
|
||||
Assert("Test 10", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz"), false);
|
||||
Assert("Test 11", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz"), false);
|
||||
Assert("Test 12", ValidateBitcoinAddress("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9"), false);
|
||||
Assert("Test 13", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I"), false);
|
||||
}
|
||||
|
||||
public static boolean ValidateBitcoinAddress(String addr) {
|
||||
if (addr.length() < 26 || addr.length() > 35) return false;
|
||||
byte[] decoded = DecodeBase58(addr, 58, 25);
|
||||
if (decoded == null) return false;
|
||||
|
||||
byte[] hash = Sha256(decoded, 0, 21, 2);
|
||||
|
||||
return Arrays.equals(Arrays.copyOfRange(hash, 0, 4), Arrays.copyOfRange(decoded, 21, 25));
|
||||
}
|
||||
|
||||
private static byte[] DecodeBase58(String input, int base, int len) {
|
||||
byte[] output = new byte[len];
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char t = input.charAt(i);
|
||||
|
||||
int p = ALPHABET.indexOf(t);
|
||||
if (p == -1) return null;
|
||||
for (int j = len - 1; j > 0; j--, p /= 256) {
|
||||
p += base * (output[j] & 0xFF);
|
||||
output[j] = (byte) (p % 256);
|
||||
}
|
||||
if (p != 0) return null;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static byte[] Sha256(byte[] data, int start, int len, int recursion) {
|
||||
if (recursion == 0) return data;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(Arrays.copyOfRange(data, start, start + len));
|
||||
return Sha256(md.digest(), 0, 32, recursion - 1);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Assert(String name, boolean value, boolean expected) {
|
||||
if (value ^ expected)
|
||||
throw new Error("Test " + name + " failed");
|
||||
else
|
||||
System.out.println(name + " passed");
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; data =
|
||||
IntegerDigits[
|
||||
FromDigits[
|
||||
StringPosition[chars, #][[1]] - 1 & /@ Characters[InputString[]],
|
||||
58], 256, 25];
|
||||
data[[-4 ;;]] ==
|
||||
IntegerDigits[
|
||||
Hash[FromCharacterCode[
|
||||
IntegerDigits[Hash[FromCharacterCode[data[[;; -5]]], "SHA256"],
|
||||
256, 32]], "SHA256"], 256, 32][[;; 4]]
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
MODULE BitcoinAddress;
|
||||
IMPORT
|
||||
Object,
|
||||
NPCT:Tools,
|
||||
Crypto:SHA256,
|
||||
S := SYSTEM,
|
||||
Out;
|
||||
|
||||
CONST
|
||||
BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
TYPE
|
||||
BC_RAW = ARRAY 25 OF CHAR;
|
||||
SHA256_HASH = ARRAY 32 OF CHAR;
|
||||
|
||||
VAR
|
||||
b58: Object.CharsLatin1;
|
||||
|
||||
PROCEDURE IndexOfB58Char(c: CHAR): INTEGER;
|
||||
VAR
|
||||
i: INTEGER;
|
||||
BEGIN
|
||||
i := 0;
|
||||
WHILE (b58[i] # 0X) & (b58[i] # c) DO INC(i) END;
|
||||
IF b58[i] = 0X THEN RETURN -1 ELSE RETURN i END
|
||||
END IndexOfB58Char;
|
||||
|
||||
PROCEDURE DecodeB58(s [NO_COPY]: ARRAY OF CHAR;VAR out: BC_RAW): BOOLEAN;
|
||||
VAR
|
||||
i,j,k: LONGINT;
|
||||
|
||||
BEGIN
|
||||
FOR i := 0 TO LEN(out) - 1 DO; out[i] := CHR(0) END;
|
||||
i := 0;
|
||||
WHILE (s[i] # 0X) DO;
|
||||
k := IndexOfB58Char(s[i]);
|
||||
IF k < 0 THEN
|
||||
Out.String("Error: Bad base58 character");Out.Ln;
|
||||
RETURN FALSE
|
||||
END;
|
||||
FOR j := LEN(out) - 1 TO 0 BY -1 DO
|
||||
k := k + 58 * ORD(out[j]);
|
||||
out[j] := CHR(k MOD 256);
|
||||
k := k DIV 256;
|
||||
END;
|
||||
|
||||
IF k # 0 THEN Out.String("Error: Address to long");Out.Ln; RETURN FALSE END;
|
||||
INC(i)
|
||||
END;
|
||||
RETURN TRUE
|
||||
END DecodeB58;
|
||||
|
||||
PROCEDURE Valid(s [NO_COPY]: ARRAY OF CHAR): BOOLEAN;
|
||||
VAR
|
||||
dec: BC_RAW;
|
||||
d1, d2: SHA256.Hash;
|
||||
d1Str, d2Str: SHA256_HASH;
|
||||
x,y: LONGINT;
|
||||
BEGIN
|
||||
Out.String(s);Out.String(" is valid? ");
|
||||
IF ~DecodeB58(s,dec) THEN RETURN FALSE END;
|
||||
|
||||
d1 := SHA256.NewHash();d1.Initialize();
|
||||
d2 := SHA256.NewHash();d2.Initialize();
|
||||
d1.Update(dec,0,21);d1.GetHash(d1Str,0);
|
||||
d2.Update(d1Str,0,d1.size);d2.GetHash(d2Str,0);
|
||||
|
||||
S.MOVE(S.ADR(dec) + 21,S.ADR(x),4);
|
||||
S.MOVE(S.ADR(d2Str),S.ADR(y),4);
|
||||
|
||||
RETURN (x = y)
|
||||
END Valid;
|
||||
|
||||
BEGIN
|
||||
b58 := Tools.AsString(BASE58);
|
||||
|
||||
Out.Bool(Valid("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9"));Out.Ln;
|
||||
Out.Bool(Valid("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"));Out.Ln;
|
||||
Out.Bool(Valid("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9"));Out.Ln;
|
||||
Out.Bool(Valid("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I"));Out.Ln
|
||||
END BitcoinAddress.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
function validate($address){
|
||||
$decoded = decodeBase58($address);
|
||||
|
||||
$d1 = hash("sha256", substr($decoded,0,21), true);
|
||||
$d2 = hash("sha256", $d1, true);
|
||||
|
||||
if(substr_compare($decoded, $d2, 21, 4)){
|
||||
throw new \Exception("bad digest");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function decodeBase58($input) {
|
||||
$alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
$out = array_fill(0, 25, 0);
|
||||
for($i=0;$i<strlen($input);$i++){
|
||||
if(($p=strpos($alphabet, $input[$i]))===false){
|
||||
throw new \Exception("invalid character found");
|
||||
}
|
||||
$c = $p;
|
||||
for ($j = 25; $j--; ) {
|
||||
$c += (int)(58 * $out[$j]);
|
||||
$out[$j] = (int)($c % 256);
|
||||
$c /= 256;
|
||||
$c = (int)$c;
|
||||
}
|
||||
if($c != 0){
|
||||
throw new \Exception("address too long");
|
||||
}
|
||||
}
|
||||
|
||||
$result = "";
|
||||
foreach($out as $val){
|
||||
$result .= chr($val);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function main () {
|
||||
$s = array(
|
||||
"1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9",
|
||||
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i",
|
||||
"1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9",
|
||||
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I",
|
||||
);
|
||||
foreach($s as $btc){
|
||||
$message = "OK";
|
||||
try{
|
||||
validate($btc);
|
||||
}catch(\Exception $e){ $message = $e->getMessage(); }
|
||||
echo "$btc: $message\n";
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -1,25 +1,17 @@
|
|||
enum B58 <
|
||||
1 2 3 4 5 6 7 8 9
|
||||
A B C D E F G H J K L M N P Q R S T U V W X Y Z
|
||||
a b c d e f g h i j k m n o p q r s t u v w x y z
|
||||
>;
|
||||
|
||||
sub unbase58(Str $str) {
|
||||
my @out = 0 xx 25;
|
||||
for B58.enums.hash{$str.comb} {
|
||||
my $c = $_;
|
||||
for reverse ^25 {
|
||||
$c += 58 * @out[$_];
|
||||
@out[$_] = $c % 256;
|
||||
$c div= 256;
|
||||
}
|
||||
}
|
||||
return @out;
|
||||
my regex bitcoin-address {
|
||||
<+alnum-[0IOl]> ** 26..* # an address is at least 26 characters long
|
||||
<?{
|
||||
use Digest::SHA;
|
||||
.subbuf(21, 4) eqv sha256(sha256 .subbuf(0, 21)).subbuf(0, 4) given
|
||||
Blob.new: <
|
||||
1 2 3 4 5 6 7 8 9
|
||||
A B C D E F G H J K L M N P Q R S T U V W X Y Z
|
||||
a b c d e f g h i j k m n o p q r s t u v w x y z
|
||||
>.pairs.invert.hash{$/.comb}
|
||||
.reduce(* * 58 + *)
|
||||
.polymod(256 xx 24)
|
||||
.reverse;
|
||||
}>
|
||||
}
|
||||
|
||||
sub check-bitcoin-address($addr) {
|
||||
use Digest::SHA;
|
||||
my @byte = unbase58 $addr;
|
||||
!!! 'wrong checksum' unless @byte[21..24] ~~
|
||||
sha256(sha256 Buf.new: @byte[0..20]).subbuf(0, 4).list;
|
||||
}
|
||||
say "Here is a bitcoin address: 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" ~~ /<bitcoin-address>/;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,9 @@ def decode_base58(bc, length):
|
|||
for char in bc:
|
||||
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]
|
||||
|
||||
if __name__ == '__main__':
|
||||
bc = '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'
|
||||
assert check_bc(bc)
|
||||
assert not check_bc( bc.replace('N', 'P', 1) )
|
||||
assert check_bc('1111111111111111111114oLvT2')
|
||||
assert check_bc("17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j")
|
||||
print(check_bc('1AGNa15ZQXAZUgFiqJ3i7Z2DPU2J6hW62i'))
|
||||
print(check_bc("17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j"))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue