|
| 1 | +const PrivateKey = @import("../bips/bip32/key.zig").PrivateKey; |
| 2 | +const Network = @import("../bips/bip32/bip32.zig").Network; |
| 3 | +const std = @import("std"); |
| 4 | +const Base58Encoder = @import("../base58/base58.zig").Encoder; |
| 5 | +const secp256k1 = @import("secp256k1"); |
| 6 | + |
| 7 | +/// WIF as defined in https://en.bitcoin.it/wiki/Wallet_import_format |
| 8 | +pub const WIF_PREFIX_MAINNET: u8 = 0x80; |
| 9 | +pub const WIF_PREFIX_TESTNET: u8 = 0xef; |
| 10 | +pub const WIF_COMPRESSED_FLAG: u8 = 0x01; |
| 11 | + |
| 12 | +pub const WIFDecodeError = error{}; |
| 13 | + |
| 14 | +pub const WIF = struct { |
| 15 | + const Self = @This(); |
| 16 | + inner: []u8, |
| 17 | + |
| 18 | + pub fn fromPrivateKey(private_key: PrivateKey) !Self { |
| 19 | + const max_size = 1 + 32 + 1 + 4; // prefix + key + compressed flag + checksum |
| 20 | + var actual_size: u8 = max_size - 1; |
| 21 | + if (private_key.compressed) { |
| 22 | + actual_size += 1; |
| 23 | + } |
| 24 | + var buf = [_]u8{0} ** max_size; |
| 25 | + |
| 26 | + if (private_key.network == Network.MAINNET) { |
| 27 | + buf[0] = WIF_PREFIX_MAINNET; |
| 28 | + } else { |
| 29 | + buf[0] = WIF_PREFIX_TESTNET; |
| 30 | + } |
| 31 | + |
| 32 | + @memcpy(buf[1..33], private_key.inner.data[0..32]); |
| 33 | + |
| 34 | + if (private_key.compressed) { |
| 35 | + buf[33] = WIF_COMPRESSED_FLAG; |
| 36 | + } |
| 37 | + |
| 38 | + var sha256 = std.crypto.hash.sha2.Sha256.init(.{}); |
| 39 | + var out256: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; |
| 40 | + |
| 41 | + sha256.update(buf[0 .. actual_size - 4]); |
| 42 | + sha256.final(&out256); |
| 43 | + |
| 44 | + sha256 = std.crypto.hash.sha2.Sha256.init(.{}); |
| 45 | + |
| 46 | + sha256.update(out256[0..std.crypto.hash.sha2.Sha256.digest_length]); |
| 47 | + sha256.final(&out256); |
| 48 | + |
| 49 | + @memcpy(buf[actual_size - 4 .. actual_size], out256[0..4]); |
| 50 | + |
| 51 | + // base58 encode |
| 52 | + var encoder = Base58Encoder{}; |
| 53 | + var encode_buf = [_]u8{0} ** 52; // max wif len is 52 |
| 54 | + const encode_size = encoder.encode(buf[0..actual_size], &encode_buf); |
| 55 | + const wif = WIF{ |
| 56 | + .inner = encode_buf[0..encode_size], |
| 57 | + }; |
| 58 | + return wif; |
| 59 | + } |
| 60 | + |
| 61 | + pub fn toString(self: WIF) []u8 { |
| 62 | + return self.inner; |
| 63 | + } |
| 64 | + |
| 65 | + pub fn fromString(wif: []const u8) !WIF { |
| 66 | + _ = wif; |
| 67 | + return WIF{ .inner = undefined }; |
| 68 | + } |
| 69 | + |
| 70 | + pub fn toPrivateKey(self: WIF) !PrivateKey { |
| 71 | + _ = self; |
| 72 | + return PrivateKey{ .network = Network.MAINNET, .compressed = false, .inner = secp256k1.SecretKey{ .data = [_]u8{0} ** 32 } }; |
| 73 | + } |
| 74 | +}; |
| 75 | + |
| 76 | +test "WIF with compressed private key" { |
| 77 | + const privateKey = PrivateKey{ .network = Network.MAINNET, .compressed = true, .inner = try secp256k1.SecretKey.fromString("7bea4d472aa93e49321bbde5db88b126b9435482e1f39d84664530a5f40408cd") }; |
| 78 | + const wif = try WIF.fromPrivateKey(privateKey); |
| 79 | + const expected = "L1NawHPsZVHsnW4DUBC7K36LzXfcsLck85fMSoEGyT4LMZv9xSjD"; |
| 80 | + const actual = wif.toString(); |
| 81 | + try std.testing.expectEqualSlices(u8, expected[0..], actual[0..]); |
| 82 | +} |
| 83 | + |
| 84 | +test "WIF with uncompressed private key 2" { |
| 85 | + const privateKey = PrivateKey{ .network = Network.MAINNET, .compressed = false, .inner = try secp256k1.SecretKey.fromString("0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D") }; |
| 86 | + const wif = try WIF.fromPrivateKey(privateKey); |
| 87 | + const expected = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"; |
| 88 | + const actual = wif.toString(); |
| 89 | + try std.testing.expectEqualSlices(u8, expected[0..], actual[0..]); |
| 90 | +} |
| 91 | + |
| 92 | +test "WIF with uncompressed private key" { |
| 93 | + const privateKey = PrivateKey{ .network = Network.MAINNET, .compressed = false, .inner = try secp256k1.SecretKey.fromString("46605abb568e1566834e7ee57e271964534d8fc3b23ca5f546b081ad7e233671") }; |
| 94 | + const wif = try WIF.fromPrivateKey(privateKey); |
| 95 | + const expected = "5JMHFZHuMcVnqVBARmg3jW3LMxdB6qbJtesC5xhXRji6wabvbWu"; |
| 96 | + const actual = wif.toString(); |
| 97 | + // std.debug.print("actual========: {s}\n", .{actual}); |
| 98 | + try std.testing.expectEqualSlices(u8, expected[0..], actual[0..]); |
| 99 | +} |
| 100 | + |
| 101 | +test "WIF with compressed testnet private key" { |
| 102 | + const privateKey = PrivateKey{ .network = Network.TESTNET, .compressed = true, .inner = try secp256k1.SecretKey.fromString("46605abb568e1566834e7ee57e271964534d8fc3b23ca5f546b081ad7e233671") }; |
| 103 | + const wif = try WIF.fromPrivateKey(privateKey); |
| 104 | + const expected = "cPwWCAXTX3NLUSq7zjzURugN5jp5FDa832H13KJNoJARUPsaTJ9G"; |
| 105 | + const actual = wif.toString(); |
| 106 | + try std.testing.expectEqualSlices(u8, expected[0..], actual[0..]); |
| 107 | +} |
| 108 | + |
| 109 | +test "WIF with uncompressed testnet private key" { |
| 110 | + const privateKey = PrivateKey{ .network = Network.TESTNET, .compressed = false, .inner = try secp256k1.SecretKey.fromString("46605abb568e1566834e7ee57e271964534d8fc3b23ca5f546b081ad7e233671") }; |
| 111 | + const wif = try WIF.fromPrivateKey(privateKey); |
| 112 | + const expected = "927uqJ7SwqZvoYgT47Zxc6bJ1cytG18WEbj9Ab42mUT9icaLVhF"; |
| 113 | + const actual = wif.toString(); |
| 114 | + try std.testing.expectEqualSlices(u8, expected[0..], actual[0..]); |
| 115 | +} |
0 commit comments