diff --git a/build.zig b/build.zig index 82a799a..c18f104 100644 --- a/build.zig +++ b/build.zig @@ -66,6 +66,11 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); + const bitcoin_zig = b.dependency("bitcoin-zig", .{ + .target = target, + .optimize = optimize, + }); + const base58_module = b.dependency("base58-zig", .{ .target = target, .optimize = optimize, @@ -190,6 +195,7 @@ pub fn build(b: *std.Build) !void { lib_unit_tests.root_module.addImport("secp256k1", secp256k1.module("secp256k1")); lib_unit_tests.root_module.linkLibrary(secp256k1.artifact("libsecp")); lib_unit_tests.root_module.addImport("httpz", httpz_module); + lib_unit_tests.root_module.addImport("bitcoin", bitcoin_zig.module("bitcoin")); lib_unit_tests.root_module.addImport("base58", base58_module); const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); @@ -214,6 +220,7 @@ pub fn build(b: *std.Build) !void { bench.root_module.addImport("zul", zul); bench.root_module.addImport("secp256k1", secp256k1.module("secp256k1")); bench.root_module.linkLibrary(secp256k1.artifact("libsecp")); + bench.root_module.addImport("bitcoin", bitcoin_zig.module("bitcoin")); const run_bench = b.addRunArtifact(bench); diff --git a/build.zig.zon b/build.zig.zon index 929c485..fa3bf1b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -47,6 +47,10 @@ .url = "https://github.com/zig-bitcoin/libsecp256k1-zig/archive/5f70bc5aa2a5ebc69c78a9a75fb83c2d7035bde1.zip", .hash = "12208e2a2f181feabb9fa01db64232e9cdefdf1b8f2919f1dd7f24c2393cad2b947b", }, + .@"bitcoin-zig" = .{ + .url = "git+https://github.com/zig-bitcoin/bitcoin-zig#f3af13008b088796697fc656e26d8c2ddf73dc18", + .hash = "1220d90650e0907125bc6035e3e10e10c088e9eb4de958b343a3cce5d7209ee5bbd4", + }, }, .paths = .{ "build.zig", diff --git a/src/bech32/bech32.zig b/src/bech32/bech32.zig deleted file mode 100644 index e9b4aa8..0000000 --- a/src/bech32/bech32.zig +++ /dev/null @@ -1,554 +0,0 @@ -const std = @import("std"); - -const Case = enum { - upper, - lower, - none, -}; - -/// Check if the HRP is valid. Returns the case of the HRP, if any. -/// -/// # Errors -/// * **MixedCase**: If the HRP contains both uppercase and lowercase characters. -/// * **InvalidChar**: If the HRP contains any non-ASCII characters (outside 33..=126). -/// * **InvalidLength**: If the HRP is outside 1..83 characters long. -fn checkHrp(hrp: []const u8) Error!Case { - if (hrp.len == 0 or hrp.len > 83) return Error.InvalidLength; - - var has_lower: bool = false; - var has_upper: bool = false; - - for (hrp) |b| { - // Valid subset of ASCII - if (!(b >= 33 and b <= 126)) return Error.InvalidChar; - - if (b >= 'a' and b <= 'z') has_lower = true else if (b >= 'A' and b <= 'Z') has_upper = true; - - if (has_lower and has_upper) return Error.MixedCase; - } - if (has_upper) return .upper; - if (has_lower) return .lower; - - return .none; -} - -fn verifyChecksum(allocator: std.mem.Allocator, hrp: []const u8, data: []const u5) Error!?Variant { - var exp = try hrpExpand(allocator, hrp); - defer exp.deinit(); - - try exp.appendSlice(data); - return Variant.fromRemainder(polymod(exp.items)); -} - -fn hrpExpand(allocator: std.mem.Allocator, hrp: []const u8) Error!std.ArrayList(u5) { - var v = std.ArrayList(u5).init(allocator); - errdefer v.deinit(); - - for (hrp) |b| { - try v.append(@truncate(b >> 5)); - } - - try v.append(0); - - for (hrp) |b| { - try v.append(@truncate(b & 0x1f)); - } - - return v; -} - -/// Generator coefficients -const GEN: [5]u32 = .{ - 0x3b6a_57b2, - 0x2650_8e6d, - 0x1ea1_19fa, - 0x3d42_33dd, - 0x2a14_62b3, -}; - -fn polymod(values: []const u5) u32 { - var chk: u32 = 1; - var b: u8 = undefined; - for (values) |v| { - b = @truncate(chk >> 25); - chk = (chk & 0x01ff_ffff) << 5 ^ @as(u32, v); - - for (GEN, 0..) |item, i| { - if (std.math.shr(u8, b, i) & 1 == 1) { - chk ^= item; - } - } - } - - return chk; -} - -/// Human-readable part and data part separator -const SEP: u8 = '1'; - -/// Encoding character set. Maps data value -> char -const CHARSET: [32]u8 = .{ - 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', // +0 - 'g', 'f', '2', 't', 'v', 'd', 'w', '0', // +8 - 's', '3', 'j', 'n', '5', '4', 'k', 'h', // +16 - 'c', 'e', '6', 'm', 'u', 'a', '7', 'l', // +24 -}; - -/// Reverse character set. Maps ASCII byte -> CHARSET index on [0,31] -const CHARSET_REV: [128]i8 = .{ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, - 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, - -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, - 6, 4, 2, -1, -1, -1, -1, -1, -}; - -/// Error types for Bech32 encoding / decoding -pub const Error = std.mem.Allocator.Error || error{ - /// String does not contain the separator character - MissingSeparator, - /// The checksum does not match the rest of the data - InvalidChecksum, - /// The data or human-readable part is too long or too short - InvalidLength, - /// Some part of the string contains an invalid character - InvalidChar, - /// Some part of the data has an invalid value - InvalidData, - /// The bit conversion failed due to a padding issue - InvalidPadding, - /// The whole string must be of one case - MixedCase, -}; - -const BECH32_CONST: u32 = 1; -const BECH32M_CONST: u32 = 0x2bc8_30a3; - -/// Used for encode/decode operations for the two variants of Bech32 -pub const Variant = enum { - /// The original Bech32 described in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) - bech32, - /// The improved Bech32m variant described in [BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) - bech32m, - - // Produce the variant based on the remainder of the polymod operation - fn fromRemainder(c: u32) ?Variant { - return switch (c) { - BECH32_CONST => .bech32, - BECH32M_CONST => .bech32m, - else => null, - }; - } - - fn constant(self: Variant) u32 { - return switch (self) { - .bech32 => BECH32_CONST, - .bech32m => BECH32M_CONST, - }; - } -}; - -/// Decode a bech32 string into the raw HRP and the `u5` data. -fn splitAndDecode(allocator: std.mem.Allocator, s: []const u8) Error!struct { std.ArrayList(u8), std.ArrayList(u5) } { - // Split at separator and check for two pieces - - const raw_hrp, const raw_data = if (std.mem.indexOfScalar(u8, s, SEP)) |sep| .{ - s[0..sep], s[sep + 1 ..], - } else return Error.MissingSeparator; - - var case = try checkHrp(raw_hrp); - var buf = try std.ArrayList(u8).initCapacity(allocator, 100); - errdefer buf.deinit(); - - const hrp_lower = switch (case) { - .upper => std.ascii.lowerString(buf.items, raw_hrp), - // already lowercase - .lower, .none => v: { - try buf.appendSlice(raw_hrp); - break :v buf.items; - }, - }; - - buf.items.len = hrp_lower.len; - - var data = std.ArrayList(u5).init(allocator); - errdefer data.deinit(); - - // Check data payload - for (raw_data) |c| { - // Only check if c is in the ASCII range, all invalid ASCII - // characters have the value -1 in CHARSET_REV (which covers - // the whole ASCII range) and will be filtered out later. - if (!std.ascii.isAscii(c)) return error.InvalidChar; - - if (std.ascii.isLower(c)) { - switch (case) { - .upper => return Error.MixedCase, - .none => case = .lower, - .lower => {}, - } - } else if (std.ascii.isUpper(c)) { - switch (case) { - .lower => return Error.MixedCase, - .none => case = .upper, - .upper => {}, - } - } - - // c should be <128 since it is in the ASCII range, CHARSET_REV.len() == 128 - const num_value = CHARSET_REV[c]; - - if (!(0 >= num_value or num_value <= 31)) return Error.InvalidChar; - - try data.append(@intCast(num_value)); - } - - return .{ buf, data }; -} - -const CHECKSUM_LENGTH: usize = 6; - -/// Decode a bech32 string into the raw HRP and the data bytes. -/// -/// Returns the HRP in lowercase, the data with the checksum removed, and the encoding. -pub fn decode(allocator: std.mem.Allocator, s: []const u8) Error!struct { std.ArrayList(u8), std.ArrayList(u5), Variant } { - const hrp_lower, var data = try splitAndDecode(allocator, s); - errdefer data.deinit(); - errdefer hrp_lower.deinit(); - - if (data.items.len < CHECKSUM_LENGTH) - return Error.InvalidLength; - - if (try verifyChecksum(allocator, hrp_lower.items, data.items)) |v| { - // Remove checksum from data payload - data.items.len = data.items.len - CHECKSUM_LENGTH; - - return .{ hrp_lower, data, v }; - } - return Error.InvalidChecksum; -} - -/// Encode a bech32 payload to an [WriteAny]. -/// -/// # Errors -/// * If [checkHrp] returns an error for the given HRP. -/// # Deviations from standard -/// * No length limits are enforced for the data part -pub fn encodeToFmt( - allocator: std.mem.Allocator, - fmt: std.io.AnyWriter, - hrp: []const u8, - data: []const u5, - variant: Variant, -) !void { - var hrp_lower = try std.ArrayList(u8).initCapacity(allocator, hrp.len); - defer hrp_lower.deinit(); - - hrp_lower.appendSliceAssumeCapacity(hrp); - - _ = if (try checkHrp(hrp) == .upper) std.ascii.lowerString(hrp_lower.items, hrp); - - var writer = try Bech32Writer.init(hrp_lower.items, variant, fmt); - - try writer.write(data); - try writer.finalize(); -} - -/// Allocationless Bech32 writer that accumulates the checksum data internally and writes them out -/// in the end. -pub const Bech32Writer = struct { - formatter: std.io.AnyWriter, - chk: u32, - variant: Variant, - - /// Creates a new writer that can write a bech32 string without allocating itself. - /// - /// This is a rather low-level API and doesn't check the HRP or data length for standard - /// compliance. - pub fn init(hrp: []const u8, variant: Variant, fmt: std.io.AnyWriter) !Bech32Writer { - var writer = Bech32Writer{ - .formatter = fmt, - .chk = 1, - .variant = variant, - }; - - _ = try writer.formatter.write(hrp); - try writer.formatter.writeByte(SEP); - - // expand HRP - for (hrp) |b| { - writer.polymodStep(@truncate(b >> 5)); - } - - writer.polymodStep(0); - for (hrp) |b| { - writer.polymodStep(@truncate(b & 0x1f)); - } - - return writer; - } - - fn polymodStep(self: *@This(), v: u5) void { - const b: u8 = @truncate(self.chk >> 25); - - self.chk = (self.chk & 0x01ff_ffff) << 5 ^ v; - - for (0.., GEN) |i, item| { - if (std.math.shr(u8, b, i) & 1 == 1) { - self.chk ^= item; - } - } - } - - pub fn finalize(self: *@This()) !void { - try self.writeChecksum(); - } - - fn writeChecksum(self: *@This()) !void { - // Pad with 6 zeros - for (0..CHECKSUM_LENGTH) |_| { - self.polymodStep(0); - } - - const plm: u32 = self.chk ^ self.variant.constant(); - - for (0..CHECKSUM_LENGTH) |p| { - const v: u8 = @intCast(std.math.shr(u32, plm, (5 * (5 - p))) & 0x1f); - - try self.formatter.writeByte(CHARSET[v]); - } - } - - /// Write a `u5` slice - fn write(self: *@This(), data: []const u5) !void { - for (data) |b| { - try self.writeU5(b); - } - } - - /// Writes a single 5 bit value of the data part - fn writeU5(self: *@This(), data: u5) !void { - self.polymodStep(data); - - try self.formatter.writeByte(CHARSET[data]); - } -}; - -// Encode a bech32 payload to string. -// -// # Errors -// * If [check_hrp] returns an error for the given HRP. -// # Deviations from standard -// * No length limits are enforced for the data part -pub fn encode(allocator: std.mem.Allocator, hrp: []const u8, data: []const u5, variant: Variant) !std.ArrayList(u8) { - var buf = std.ArrayList(u8).init(allocator); - errdefer buf.deinit(); - - try encodeToFmt(allocator, buf.writer().any(), hrp, data, variant); - - return buf; -} - -pub fn toBase32(allocator: std.mem.Allocator, d: []const u8) !std.ArrayList(u5) { - var self = std.ArrayList(u5).init(allocator); - errdefer self.deinit(); - - // Amount of bits left over from last round, stored in buffer. - var buffer_bits: u32 = 0; - // Holds all unwritten bits left over from last round. The bits are stored beginning from - // the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will - // look as follows: [a, b, c, 0, 0, 0, 0, 0] - var buffer: u8 = 0; - - for (d) |b| { - // Write first u5 if we have to write two u5s this round. That only happens if the - // buffer holds too many bits, so we don't have to combine buffer bits with new bits - // from this rounds byte. - if (buffer_bits >= 5) { - try self.append(@truncate(std.math.shr(u8, buffer & 0b1111_1000, 3))); - buffer <<= 5; - buffer_bits -= 5; - } - - // Combine all bits from buffer with enough bits from this rounds byte so that they fill - // a u5. Save reamining bits from byte to buffer. - const from_buffer = buffer >> 3; - const from_byte = std.math.shr(u8, b, 3 + buffer_bits); // buffer_bits <= 4 - - try self.append(@truncate(from_buffer | from_byte)); - buffer = std.math.shl(u8, b, 5 - buffer_bits); - buffer_bits += 3; - } - - // There can be at most two u5s left in the buffer after processing all bytes, write them. - if (buffer_bits >= 5) { - try self.append(@truncate((buffer & 0b1111_1000) >> 3)); - buffer <<= 5; - buffer_bits -= 5; - } - - if (buffer_bits != 0) { - try self.append(@truncate(buffer >> 3)); - } - - return self; -} - -/// Encode a bech32 payload without a checksum to an [std.io.AnyWriter]. -/// -/// # Errors -/// * If [checkHrp] returns an error for the given HRP. -/// # Deviations from standard -/// * No length limits are enforced for the data part -pub fn encodeWithoutChecksumToFmt( - allocator: std.mem.Allocator, - fmt: std.io.AnyWriter, - hrp: []const u8, - data: []const u5, -) !void { - var hrp_lower = try std.ArrayList(u8).initCapacity(allocator, hrp.len); - defer hrp_lower.deinit(); - - hrp_lower.appendSliceAssumeCapacity(hrp); - - _ = if (try checkHrp(hrp) == .upper) std.ascii.lowerString(hrp_lower.items, hrp); - - _ = try fmt.write(hrp); - - _ = try fmt.writeByte(SEP); - - for (data) |b| { - try fmt.writeByte(CHARSET[b]); - } -} - -/// Encode a bech32 payload to string without the checksum. -/// -/// # Errors -/// * If [checkHrp] returns an error for the given HRP. -/// # Deviations from standard -/// * No length limits are enforced for the data part -pub fn encodeWithoutChecksum(allocator: std.mem.Allocator, hrp: []const u8, data: []const u5) !std.ArrayList(u8) { - var buf = std.ArrayList(u8).init(allocator); - errdefer buf.deinit(); - - try encodeWithoutChecksumToFmt(allocator, buf.writer().any(), hrp, data); - - return buf; -} - -/// Decode a bech32 string into the raw HRP and the data bytes, assuming no checksum. -/// -/// Returns the HRP in lowercase and the data. -pub fn decodeWithoutChecksum(allocator: std.mem.Allocator, s: []const u8) Error!struct { std.ArrayList(u8), std.ArrayList(u5) } { - return splitAndDecode(allocator, s); -} - -/// Convert base32 to base256, removes null-padding if present, returns -/// `Err(Error::InvalidPadding)` if padding bits are unequal `0` -pub fn arrayListFromBase32(allocator: std.mem.Allocator, b: []const u5) !std.ArrayList(u8) { - return convertBits(u5, allocator, b, 5, 8, false); -} - -/// Convert between bit sizes -/// -/// # Errors -/// * `Error::InvalidData` if any element of `data` is out of range -/// * `Error::InvalidPadding` if `pad == false` and the padding bits are not `0` -/// -/// # Panics -/// Function will panic if attempting to convert `from` or `to` a bit size that -/// is 0 or larger than 8 bits. -/// -/// # Examples -/// -/// ```zig -/// const base5 = try convertBits(u8, allocator, &.{0xff}, 8, 5, true); -/// std.testing.expectEqualSlices(u8, base5.items, &.{0x1f, 0x1c}); -/// ``` -pub fn convertBits(comptime T: type, allocator: std.mem.Allocator, data: []const T, from: u32, to: u32, pad: bool) !std.ArrayList(u8) { - if (from > 8 or to > 8 or from == 0 or to == 0) { - @panic("convert_bits `from` and `to` parameters 0 or greater than 8"); - } - - var acc: u32 = 0; - var bits: u32 = 0; - var ret = std.ArrayList(u8).init(allocator); - errdefer ret.deinit(); - - const maxv: u32 = std.math.shl(u32, 1, to) - 1; - for (data) |value| { - const v: u32 = @intCast(value); - if (std.math.shr(u32, v, from) != 0) { - // Input value exceeds `from` bit size - return error.InvalidData; - } - acc = std.math.shl(u32, acc, from) | v; - bits += from; - - while (bits >= to) { - bits -= to; - try ret.append(@truncate(std.math.shr(u32, acc, bits) & maxv)); - } - } - - if (pad) { - if (bits > 0) { - try ret.append(@truncate(std.math.shl(u32, acc, to - bits) & maxv)); - } - } else if (bits >= from or (std.math.shl(u32, acc, to - bits) & maxv) != 0) { - return error.InvalidPadding; - } - - return ret; -} - -test "encode" { - try std.testing.expectError( - error.InvalidLength, - encode(std.testing.allocator, "", &.{ 1, 2, 3, 4 }, .bech32), - ); -} - -test "roundtrip_without_checksum" { - const hrp = "lnbc"; - const data = try toBase32(std.testing.allocator, "Hello World!"); - defer data.deinit(); - - const encoded = try encodeWithoutChecksum(std.testing.allocator, hrp, data.items); - defer encoded.deinit(); - - const decoded_hrp, const decoded_data = - try decodeWithoutChecksum(std.testing.allocator, encoded.items); - defer decoded_hrp.deinit(); - defer decoded_data.deinit(); - - try std.testing.expectEqualSlices(u8, hrp, decoded_hrp.items); - - try std.testing.expectEqualSlices(u5, data.items, decoded_data.items); -} - -test "test_hrp_case_decode" { - const hrp, const data, const variant = try decode(std.testing.allocator, "hrp1qqqq40atq3"); - defer hrp.deinit(); - defer data.deinit(); - - var expected_data = try toBase32(std.testing.allocator, &.{ 0x00, 0x00 }); - defer expected_data.deinit(); - - try std.testing.expectEqual(.bech32, variant); - try std.testing.expectEqualSlices(u8, "hrp", hrp.items); - try std.testing.expectEqualSlices(u5, expected_data.items, data.items); -} - -test "test_hrp_case" { - var data = try toBase32(std.testing.allocator, &.{ 0x00, 0x00 }); - defer data.deinit(); - - // Tests for issue with HRP case checking being ignored for encoding - const encoded = try encode(std.testing.allocator, "HRP", data.items, .bech32); - defer encoded.deinit(); - - try std.testing.expectEqualSlices(u8, "hrp1qqqq40atq3", encoded.items); -} diff --git a/src/core/bip32/bip32.zig b/src/core/bip32/bip32.zig deleted file mode 100644 index 3804f28..0000000 --- a/src/core/bip32/bip32.zig +++ /dev/null @@ -1,774 +0,0 @@ -//! BIP32 implementation. -//! -//! Implementation of BIP32 hierarchical deterministic wallets, as defined -//! at . -//! -const Ripemd160 = @import("../../mint/lightning/invoices/ripemd160.zig").Ripemd160; -const secp256k1 = @import("secp256k1"); -const Secp256k1NumberOfPoints = 115792089237316195423570985008687907852837564279074904382605163141518161494337; -const key_lib = @import("key.zig"); - -const base58 = @import("base58"); - -const std = @import("std"); -const Hmac = std.crypto.auth.hmac.sha2.HmacSha512; - -pub const Network = enum { MAINNET, TESTNET, REGTEST, SIMNET }; - -pub const SerializedPrivateKeyVersion = enum(u32) { - MAINNET = 0x0488aDe4, - TESTNET = 0x04358394, - SEGWIT_MAINNET = 0x04b2430c, - SEGWIT_TESTNET = 0x045f18bc, -}; - -pub const SerializedPublicKeyVersion = enum(u32) { - MAINNET = 0x0488b21e, - TESTNET = 0x043587cf, - SEGWIT_MAINNET = 0x04b24746, - SEGWIT_TESTNET = 0x045f1cf6, -}; - -/// A chain code -pub const ChainCode = struct { - inner: [32]u8, - - fn fromHmac(hmac: [64]u8) ChainCode { - return .{ .inner = hmac[32..].* }; - } -}; - -/// A fingerprint -pub const Fingerprint = struct { - inner: [4]u8, -}; - -fn base58EncodeCheck(allocator: std.mem.Allocator, data: []const u8) ![]u8 { - const encoder = base58.Encoder.init(.{}); - - var hasher = std.crypto.hash.sha2.Sha256.init(.{}); - hasher.update(data); - var checksum = hasher.finalResult(); - - hasher = std.crypto.hash.sha2.Sha256.init(.{}); - hasher.update(&checksum); - checksum = hasher.finalResult(); - - var encoding_data = try allocator.alloc(u8, data.len + 4); - defer allocator.free(encoding_data); - - @memcpy(encoding_data[0..data.len], data); - @memcpy(encoding_data[data.len..], checksum[0..4]); - - return try encoder.encodeAlloc(allocator, encoding_data); -} - -fn base58DecodeCheck(allocator: std.mem.Allocator, data: []const u8) ![]u8 { - const decoder = base58.Decoder.init(.{}); - - const decoded = try decoder.decodeAlloc(allocator, data); - defer allocator.free(decoded); - if (decoded.len < 4) return error.TooShortError; - - const check_start = decoded.len - 4; - - var hasher = std.crypto.hash.sha2.Sha256.init(.{}); - - hasher.update(decoded[0..check_start]); - const fr = hasher.finalResult(); - - hasher = std.crypto.hash.sha2.Sha256.init(.{}); - hasher.update(&fr); - - const hash_check = hasher.finalResult()[0..4].*; - const data_check = decoded[check_start..][0..4].*; - - const expected = std.mem.readInt(u32, &hash_check, .little); - const actual = std.mem.readInt(u32, &data_check, .little); - - if (expected != actual) return error.IncorrectChecksum; - - const result = try allocator.alloc(u8, check_start); - errdefer allocator.free(result); - - @memcpy(result, decoded[0..check_start]); - return result; -} - -/// Extended private key -pub const ExtendedPrivKey = struct { - /// The network this key is to be used on - network: Network, - /// How many derivations this key is from the master (which is 0) - depth: u8, - /// Fingerprint of the parent key (0 for master) - parent_fingerprint: Fingerprint, - /// Child number of the key used to derive from parent (0 for master) - child_number: ChildNumber, - /// Private key - private_key: secp256k1.SecretKey, - /// Chain code - chain_code: ChainCode, - - pub fn fromStr(allocator: std.mem.Allocator, s: []const u8) !ExtendedPrivKey { - const decoded = try base58DecodeCheck(allocator, s); - defer allocator.free(decoded); - - if (decoded.len != 78) return error.InvalidLength; - - return try decode(decoded); - } - - pub fn toStr(self: ExtendedPrivKey, allocator: std.mem.Allocator) ![]const u8 { - const encoded = self.encode(); - return base58EncodeCheck(allocator, &encoded); - } - - /// Extended private key binary encoding according to BIP 32 - pub fn encode(self: ExtendedPrivKey) [78]u8 { - var ret = [_]u8{0} ** 78; - - ret[0..4].* = switch (self.network) { - .MAINNET => .{ 0x04, 0x88, 0xAD, 0xE4 }, - else => .{ 0x04, 0x35, 0x83, 0x94 }, - }; - - ret[4] = self.depth; - ret[5..9].* = self.parent_fingerprint.inner; - - var buf: [4]u8 = undefined; - std.mem.writeInt(u32, &buf, self.child_number.toU32(), .big); - - ret[9..13].* = buf; - ret[13..45].* = self.chain_code.inner; - ret[45] = 0; - ret[46..78].* = self.private_key.data; - return ret; - } - - /// Construct a new master key from a seed value - pub fn initMaster(network: Network, seed: []const u8) !ExtendedPrivKey { - var hmac_engine = Hmac.init("Bitcoin seed"); - hmac_engine.update(seed); - var hmac_result: [Hmac.mac_length]u8 = undefined; - - hmac_engine.final(&hmac_result); - - return ExtendedPrivKey{ - .network = network, - .depth = 0, - .parent_fingerprint = .{ .inner = .{ 0, 0, 0, 0 } }, - .child_number = try ChildNumber.fromNormalIdx(0), - .private_key = try secp256k1.SecretKey.fromSlice(hmac_result[0..32]), - .chain_code = ChainCode.fromHmac(hmac_result), - }; - } - - /// Constructs ECDSA compressed private key matching internal secret key representation. - pub fn toPrivateKey(self: ExtendedPrivKey) key_lib.PrivateKey { - return .{ - .compressed = true, - .network = self.network, - .inner = self.private_key, - }; - } - - /// Constructs BIP340 keypair for Schnorr signatures and Taproot use matching the internal - /// secret key representation. - pub fn toKeypair(self: ExtendedPrivKey, secp: secp256k1.Secp256k1) secp256k1.KeyPair { - return secp256k1.KeyPair.fromSecretKey(&secp, &self.private_key) catch @panic("BIP32 internal private key representation is broken"); - } - - /// Private->Private child key derivation - pub fn ckdPriv( - self: ExtendedPrivKey, - secp: secp256k1.Secp256k1, - i: ChildNumber, - ) !ExtendedPrivKey { - var hmac_engine = Hmac.init(self.chain_code.inner[0..]); - switch (i) { - .normal => { - // Non-hardened key: compute public data and use that - hmac_engine.update(&self.private_key.publicKey(secp).serialize()); - }, - .hardened => { - // Hardened key: use only secret data to prevent public derivation - hmac_engine.update(&.{0}); - hmac_engine.update(self.private_key.data[0..]); - }, - } - - const i_u32 = i.toU32(); - var buf: [4]u8 = undefined; - - std.mem.writeInt(u32, &buf, i_u32, .big); - - hmac_engine.update(&buf); - - var hmac_result: [Hmac.mac_length]u8 = undefined; - - hmac_engine.final(&hmac_result); - - const sk = secp256k1.SecretKey.fromSlice(hmac_result[0..32]) catch @panic("statistically impossible to hit"); - const tweaked = sk.addTweak(secp256k1.Scalar.fromSecretKey(self.private_key)) catch @panic("statistically impossible to hit"); - - return .{ - .network = self.network, - .depth = self.depth + 1, - .parent_fingerprint = self.fingerprint(secp), - .child_number = i, - .private_key = tweaked, - .chain_code = ChainCode.fromHmac(hmac_result), - }; - } - - /// Attempts to derive an extended private key from a path. - /// - /// The `path` argument can be both of type `DerivationPath` or `Vec`. - pub fn derivePriv( - self: ExtendedPrivKey, - secp: secp256k1.Secp256k1, - path: []const ChildNumber, - ) !ExtendedPrivKey { - var sk = self; - for (path) |cnum| { - sk = try sk.ckdPriv(secp, cnum); - } - - return sk; - } - - /// Returns the HASH160 of the public key belonging to the xpriv - pub fn identifier(self: ExtendedPrivKey, secp: secp256k1.Secp256k1) XpubIdentifier { - return ExtendedPubKey.fromPrivateKey(secp, self).identifier(); - } - - /// Returns the first four bytes of the identifier - pub fn fingerprint(self: ExtendedPrivKey, secp: secp256k1.Secp256k1) Fingerprint { - return .{ .inner = self.identifier(secp).inner[0..4].* }; - } - - /// Decoding extended private key from binary data according to BIP 32 - pub fn decode(data: []const u8) !ExtendedPrivKey { - if (data.len != 78) { - return error.WrongExtendedKeyLength; - } - - const network = if (std.mem.eql(u8, data[0..4], &.{ 0x04, 0x88, 0xAD, 0xE4 })) - Network.MAINNET - else if (std.mem.eql(u8, data[0..4], &.{ 0x04, 0x35, 0x83, 0x94 })) - Network.TESTNET - else - return error.UnknownVersion; - - return .{ - .network = network, - .depth = data[4], - .parent_fingerprint = .{ .inner = data[5..9].* }, - .child_number = ChildNumber.fromU32(std.mem.readInt(u32, data[9..13], .big)), - .chain_code = .{ .inner = data[13..45].* }, - .private_key = try secp256k1.SecretKey.fromSlice(data[46..78]), - }; - } -}; - -/// Extended public key -pub const ExtendedPubKey = struct { - /// The network this key is to be used on - network: Network, - /// How many derivations this key is from the master (which is 0) - depth: u8, - /// Fingerprint of the parent key - parent_fingerprint: Fingerprint, - /// Child number of the key used to derive from parent (0 for master) - child_number: ChildNumber, - /// Public key - public_key: secp256k1.PublicKey, - /// Chain code - chain_code: ChainCode, - - pub fn fromStr(allocator: std.mem.Allocator, s: []const u8) !ExtendedPubKey { - const decoded = try base58DecodeCheck(allocator, s); - defer allocator.free(decoded); - - if (decoded.len != 78) return error.InvalidLength; - - return try decode(decoded); - } - - pub fn toStr(self: ExtendedPubKey, allocator: std.mem.Allocator) ![]const u8 { - return try base58EncodeCheck(allocator, &self.encode()); - } - - /// Extended public key binary encoding according to BIP 32 - pub fn encode(self: ExtendedPubKey) [78]u8 { - var ret = [_]u8{0} ** 78; - - ret[0..4].* = switch (self.network) { - .MAINNET => .{ 0x04, 0x88, 0xB2, 0x1E }, - else => .{ 0x04, 0x35, 0x87, 0xCF }, - }; - - ret[4] = self.depth; - ret[5..9].* = self.parent_fingerprint.inner; - - var buf: [4]u8 = undefined; - std.mem.writeInt(u32, &buf, self.child_number.toU32(), .big); - - ret[9..13].* = buf; - ret[13..45].* = self.chain_code.inner; - ret[45..78].* = self.public_key.serialize(); - return ret; - } - - pub fn decode(data: []const u8) !ExtendedPubKey { - if (data.len != 78) { - return error.WrongExtendedKeyLength; - } - - const network = if (std.mem.eql(u8, data[0..4], &.{ 0x04, 0x88, 0xB2, 0x1E })) - Network.MAINNET - else if (std.mem.eql(u8, data[0..4], &.{ 0x04, 0x35, 0x87, 0xCF })) - Network.TESTNET - else - return error.UnknownVersion; - - return .{ - .network = network, - .depth = data[4], - .parent_fingerprint = .{ .inner = data[5..9].* }, - .child_number = ChildNumber.fromU32(std.mem.readInt(u32, data[9..13], .big)), - .chain_code = .{ .inner = data[13..45].* }, - .public_key = try secp256k1.PublicKey.fromSlice(data[45..78]), - }; - } - - /// Derives a public key from a private key - pub fn fromPrivateKey( - secp: secp256k1.Secp256k1, - sk: ExtendedPrivKey, - ) ExtendedPubKey { - return .{ - .network = sk.network, - .depth = sk.depth, - .parent_fingerprint = sk.parent_fingerprint, - .child_number = sk.child_number, - .public_key = sk.private_key.publicKey(secp), - .chain_code = sk.chain_code, - }; - } - - /// Attempts to derive an extended public key from a path. - /// - /// The `path` argument can be any type implementing `AsRef`, such as `DerivationPath`, for instance. - pub fn derivePub( - self: ExtendedPubKey, - secp: secp256k1.Secp256k1, - path: []ChildNumber, - ) !ExtendedPubKey { - var pk = self; - for (path) |cnum| { - pk = try pk.ckdPub(secp, cnum); - } - - return pk; - } - - /// Compute the scalar tweak added to this key to get a child key - pub fn ckdPubTweak( - self: ExtendedPubKey, - i: ChildNumber, - ) !struct { secp256k1.SecretKey, ChainCode } { - switch (i) { - .hardened => return error.CannotDeriveFromHardenedKey, - .normal => |n| { - var hmac_engine = Hmac.init(&self.chain_code.inner); - - hmac_engine.update(&self.public_key.serialize()); - - var buf: [4]u8 = undefined; - std.mem.writeInt(u32, &buf, n, .big); - - hmac_engine.update(&buf); - var hmac_result: [Hmac.mac_length]u8 = undefined; - hmac_engine.final(&hmac_result); - - const private_key = try secp256k1.SecretKey.fromSlice(hmac_result[0..32]); - const chain_code = ChainCode.fromHmac(hmac_result); - - return .{ private_key, chain_code }; - }, - } - } - - /// Public->Public child key derivation - pub fn ckdPub( - self: ExtendedPubKey, - secp: secp256k1.Secp256k1, - i: ChildNumber, - ) !ExtendedPubKey { - const sk, const chain_code = try self.ckdPubTweak(i); - - const tweaked = try self.public_key.addExpTweak(secp, secp256k1.Scalar.fromSecretKey(sk)); - - return .{ - .network = self.network, - .depth = self.depth + 1, - .parent_fingerprint = self.fingerprint(), - .child_number = i, - .public_key = tweaked, - .chain_code = chain_code, - }; - } - - /// Returns the HASH160 of the chaincode - pub fn identifier(self: ExtendedPubKey) XpubIdentifier { - return .{ .inner = hash160(&self.public_key.serialize()) }; - } - - /// Returns the first four bytes of the identifier - pub fn fingerprint(self: ExtendedPubKey) Fingerprint { - return .{ .inner = self.identifier().inner[0..4].* }; - } -}; - -fn hash160(data: []const u8) [Ripemd160.digest_length]u8 { - var hasher256 = std.crypto.hash.sha2.Sha256.init(.{}); - hasher256.update(data); - - var out256: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; - hasher256.final(&out256); - - var hasher = Ripemd160.init(.{}); - hasher.update(&out256); - - var out: [Ripemd160.digest_length]u8 = undefined; - hasher.final(&out); - return out; -} - -pub const XpubIdentifier = struct { - inner: [Ripemd160.digest_length]u8, -}; - -/// A child number for a derived key -pub const ChildNumber = union(enum) { - /// Non-hardened key - /// Key index, within [0, 2^31 - 1] - normal: u32, - /// Hardened key - /// Key index, within [0, 2^31 - 1] - hardened: u32, - - pub fn fromStr(inp: []const u8) !ChildNumber { - const is_hardened = (inp[inp.len - 1] == '\'' or inp[inp.len - 1] == 'h'); - - if (is_hardened) return try fromHardenedIdx(try std.fmt.parseInt(u32, inp[0 .. inp.len - 1], 10)) else return try fromNormalIdx(try std.fmt.parseInt(u32, inp, 10)); - } - - /// Create a [`Normal`] from an index, returns an error if the index is not within - /// [0, 2^31 - 1]. - /// - /// [`Normal`]: #variant.Normal - pub fn fromNormalIdx(index: u32) !ChildNumber { - if ((index & (1 << 31)) == 0) - return .{ .normal = index }; - - return error.InvalidChildNumber; - } - - /// Create a [`Hardened`] from an index, returns an error if the index is not within - /// [0, 2^31 - 1]. - /// - /// [`Hardened`]: #variant.Hardened - pub fn fromHardenedIdx(index: u32) !ChildNumber { - if (index & (1 << 31) == 0) - return .{ .hardened = index }; - - return error.InvalidChildNumber; - } - - /// Returns `true` if the child number is a [`Normal`] value. - /// - /// [`Normal`]: #variant.Normal - pub fn isNormal(self: ChildNumber) bool { - return !self.isHardened(); - } - - /// Returns `true` if the child number is a [`Hardened`] value. - /// - /// [`Hardened`]: #variant.Hardened - pub fn isHardened(self: ChildNumber) bool { - return switch (self) { - .hardened => true, - .normal => false, - }; - } - /// Returns the child number that is a single increment from this one. - pub fn increment(self: ChildNumber) !ChildNumber { - return switch (self) { - .hardened => |idx| try fromHardenedIdx(idx + 1), - .normal => |idx| try fromNormalIdx(idx + 1), - }; - } - - fn fromU32(number: u32) ChildNumber { - if (number & (1 << 31) != 0) { - return .{ - .hardened = number ^ (1 << 31), - }; - } else { - return .{ .normal = number }; - } - } - - fn toU32(self: ChildNumber) u32 { - return switch (self) { - .normal => |index| index, - .hardened => |index| index | (1 << 31), - }; - } -}; - -fn testPath( - secp: secp256k1.Secp256k1, - network: Network, - seed: []const u8, - path: []ChildNumber, - expected_sk: []const u8, - expected_pk: []const u8, -) !void { - var sk = try ExtendedPrivKey.initMaster(network, seed); - var pk = ExtendedPubKey.fromPrivateKey(secp, sk); - - // Check derivation convenience method for ExtendedPrivKey - { - const actual_sk = try (try sk.derivePriv(secp, path)).toStr(std.testing.allocator); - defer std.testing.allocator.free(actual_sk); - - try std.testing.expectEqualSlices( - u8, - actual_sk, - expected_sk, - ); - } - - // Check derivation convenience method for ExtendedPubKey, should error - // appropriately if any ChildNumber is hardened - for (path) |cnum| { - if (cnum.isHardened()) { - try std.testing.expectError(error.CannotDeriveFromHardenedKey, pk.derivePub(secp, path)); - break; - } - } else { - const derivedPub = try (try pk.derivePub(secp, path)).toStr(std.testing.allocator); - defer std.testing.allocator.free(derivedPub); - - try std.testing.expectEqualSlices(u8, derivedPub, expected_pk); - } - - // Derive keys, checking hardened and non-hardened derivation one-by-one - for (path) |num| { - sk = try sk.ckdPriv(secp, num); - switch (num) { - .normal => { - const pk2 = try pk.ckdPub(secp, num); - pk = ExtendedPubKey.fromPrivateKey(secp, sk); - try std.testing.expectEqualDeep(pk, pk2); - }, - .hardened => { - try std.testing.expectError(error.CannotDeriveFromHardenedKey, pk.ckdPub(secp, num)); - pk = ExtendedPubKey.fromPrivateKey(secp, sk); - }, - } - } - // Check result against expected base58 - const skStr = try sk.toStr(std.testing.allocator); - defer std.testing.allocator.free(skStr); - try std.testing.expectEqualSlices(u8, skStr, expected_sk); - - const pkStr = try pk.toStr(std.testing.allocator); - defer std.testing.allocator.free(pkStr); - try std.testing.expectEqualSlices(u8, pkStr, expected_pk); - - // Check decoded base58 against result - const decoded_sk = try ExtendedPrivKey.fromStr(std.testing.allocator, expected_sk); - const decoded_pk = try ExtendedPubKey.fromStr(std.testing.allocator, expected_pk); - - try std.testing.expectEqualDeep(decoded_sk, sk); - try std.testing.expectEqualDeep(decoded_pk, pk); -} - -fn derivatePathFromStr(path: []const u8, allocator: std.mem.Allocator) !std.ArrayList(ChildNumber) { - if (path.len == 0 or (path.len == 1 and path[0] == 'm') or (path.len == 2 and path[0] == 'm' and path[1] == '/')) return std.ArrayList(ChildNumber).init(allocator); - - var p = path; - - if (std.mem.startsWith(u8, path, "m/")) p = path[2..]; - - var parts = std.mem.splitScalar(u8, p, '/'); - - var result = std.ArrayList(ChildNumber).init(allocator); - errdefer result.deinit(); - - while (parts.next()) |s| { - try result.append(try ChildNumber.fromStr(s)); - } - - return result; -} - -test "schnorr_broken_privkey_ffs" { - // Xpriv having secret key set to all 0xFF's - const xpriv_str = "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fENZ3QzxW"; - try std.testing.expectError(error.InvalidSecretKey, ExtendedPrivKey.fromStr(std.testing.allocator, xpriv_str)); -} - -test "vector_1" { - var secp = try secp256k1.Secp256k1.genNew(); - defer secp.deinit(); - - var buf: [100]u8 = undefined; - - const seed = try std.fmt.hexToBytes(&buf, "000102030405060708090a0b0c0d0e0f"); - // derivation path, expected_sk , expected_pk - const testSuite: []const struct { Network, []const u8, []const u8, []const u8 } = &.{ - .{ - .MAINNET, - "m", - "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", - "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", - }, - .{ - .MAINNET, - "m/0h", - "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", - "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", - }, - .{ - .MAINNET, - "m/0h/1", - "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", - "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", - }, - .{ - .MAINNET, - "m/0h/1/2h", - "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", - "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", - }, - .{ - .MAINNET, - "m/0h/1/2h/2", - "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", - "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", - }, - .{ - .MAINNET, - "m/0h/1/2h/2/1000000000", - "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", - "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", - }, - }; - - for (testSuite, 0..) |suite, idx| { - errdefer { - std.log.warn("suite failed n={d} : {any}", .{ idx + 1, suite }); - } - - const path = try derivatePathFromStr(suite[1], std.testing.allocator); - defer path.deinit(); - - try testPath(secp, .MAINNET, seed, path.items, suite[2], suite[3]); - } -} - -test "vector_2" { - var secp = try secp256k1.Secp256k1.genNew(); - defer secp.deinit(); - - var buf: [100]u8 = undefined; - - const seed = try std.fmt.hexToBytes(&buf, "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"); - // derivation path, expected_sk , expected_pk - const testSuite: []const struct { Network, []const u8, []const u8, []const u8 } = &.{ - .{ - .MAINNET, - "m", - "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", - "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", - }, - .{ - .MAINNET, - "m/0", - "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", - "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", - }, - .{ - .MAINNET, - "m/0/2147483647h", - "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", - "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", - }, - .{ - .MAINNET, - "m/0/2147483647h/1", - "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", - "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", - }, - .{ - .MAINNET, - "m/0/2147483647h/1/2147483646h", - "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", - "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", - }, - .{ - .MAINNET, - "m/0/2147483647h/1/2147483646h/2", - "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", - "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", - }, - }; - - for (testSuite, 0..) |suite, idx| { - errdefer { - std.log.warn("suite failed n={d} : {any}", .{ idx + 1, suite }); - } - - const path = try derivatePathFromStr(suite[1], std.testing.allocator); - defer path.deinit(); - - try testPath(secp, .MAINNET, seed, path.items, suite[2], suite[3]); - } -} - -test "vector_3" { - var secp = try secp256k1.Secp256k1.genNew(); - defer secp.deinit(); - - var buf: [100]u8 = undefined; - - const seed = try std.fmt.hexToBytes(&buf, "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"); - - const path_1 = try derivatePathFromStr("m", std.testing.allocator); - defer path_1.deinit(); - - // m - try testPath(secp, .MAINNET, seed, path_1.items, "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13"); - - // m/0h - const path_2 = try derivatePathFromStr("m/0h", std.testing.allocator); - defer path_2.deinit(); - - try testPath(secp, .MAINNET, seed, path_2.items, "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"); -} - -test "base58_check_decode_encode" { - const encoded = try base58EncodeCheck(std.testing.allocator, "test"); - defer std.testing.allocator.free(encoded); - - const decoded = try base58DecodeCheck(std.testing.allocator, encoded); - defer std.testing.allocator.free(decoded); - - try std.testing.expectEqualSlices(u8, decoded, "test"); -} diff --git a/src/core/bip32/key.zig b/src/core/bip32/key.zig deleted file mode 100644 index a15bb84..0000000 --- a/src/core/bip32/key.zig +++ /dev/null @@ -1,12 +0,0 @@ -const secp256k1 = @import("secp256k1"); -const Network = @import("bip32.zig").Network; - -/// A Bitcoin ECDSA private key -pub const PrivateKey = struct { - /// Whether this private key should be serialized as compressed - compressed: bool, - /// The network on which this key should be used - network: Network, - /// The actual ECDSA key - inner: secp256k1.SecretKey, -}; diff --git a/src/core/bip32/utils.zig b/src/core/bip32/utils.zig deleted file mode 100644 index 4280cd0..0000000 --- a/src/core/bip32/utils.zig +++ /dev/null @@ -1,182 +0,0 @@ -const std = @import("std"); -const math = std.math; -const unicode = std.unicode; -const base58 = @import("base58"); -const Ripemd160 = @import("crypto").Ripemd160; - -pub const DecodedCompactSize = struct { - totalBytes: u8, - n: u64, -}; - -pub const EncodedCompactSize = struct { - compactSizeByte: u8, - totalBytes: u8, - n: u64, -}; - -pub fn intToHexStr(comptime T: type, data: T, buffer: []u8) !void { - // Number of characters to represent data in hex - // log16(data) + 1 - const n: usize = if (data == 0) 1 else @intCast(math.log(T, 16, data) + 1); - const missing: usize = @intCast(buffer.len - n); - for (0..missing) |i| { - buffer[i] = '0'; - } - _ = try std.fmt.bufPrint(buffer[missing..], "{x}", .{data}); -} - -pub fn toBase58(buffer: []u8, bytes: []const u8) !void { - const encoder = base58.Encoder.init(.{}); - _ = try encoder.encode(bytes, buffer); -} - -pub fn toBase58Allocator(allocator: std.mem.Allocator, bytes: []const u8) ![]u8 { - const encoder = base58.Encoder.init(.{}); - return try encoder.encodeAlloc(allocator, bytes); -} - -pub fn fromBase58(encoded: []const u8, buffer: []u8) !void { - const decoder = base58.Decoder.init(.{}); - _ = try decoder.decode(encoded, buffer); -} - -pub fn calculateChecksum(bytes: []const u8) [4]u8 { - var buffer: [32]u8 = undefined; - std.crypto.hash.sha2.Sha256.hash(bytes, &buffer, .{}); - std.crypto.hash.sha2.Sha256.hash(&buffer, &buffer, .{}); - return buffer[0..4].*; -} - -pub fn verifyChecksum(bytes: []const u8, checksum: [4]u8) bool { - var buffer: [32]u8 = undefined; - std.crypto.hash.sha2.Sha256.hash(bytes, &buffer, .{}); - std.crypto.hash.sha2.Sha256.hash(&buffer, &buffer, .{}); - - return std.mem.eql(u8, buffer[0..4], checksum[0..4]); -} - -pub fn debugPrintBytes(comptime len: u32, bytes: []const u8) void { - var buf: [len]u8 = undefined; - _ = std.fmt.bufPrint(&buf, "{x}", .{std.fmt.fmtSliceHexLower(bytes)}) catch unreachable; - std.debug.print("DEBUG PRINT BYTES: {s}\n", .{buf}); -} - -pub fn doubleSha256(bytes: []const u8) [32]u8 { - var buffer: [32]u8 = undefined; - std.crypto.hash.sha2.Sha256.hash(bytes, &buffer, .{}); - std.crypto.hash.sha2.Sha256.hash(&buffer, &buffer, .{}); - return buffer; -} - -pub fn hash160(bytes: []const u8) [20]u8 { - var hashed: [32]u8 = undefined; - std.crypto.hash.sha2.Sha256.hash(bytes, &hashed, .{}); - const r = Ripemd160.hash(&hashed); - return r.bytes; -} - -pub fn encodeutf8(in: []const u8, buffer: []u8) !u16 { - const v = try unicode.Utf8View.init(in); - var it = v.iterator(); - var cur: u16 = 0; - while (it.nextCodepoint()) |codepoint| { - var b: [4]u8 = undefined; - const len: u16 = @as(u16, try unicode.utf8Encode(codepoint, &b)); - @memcpy(buffer[cur .. cur + len], b[0..len]); - cur += len; - } - return cur; -} - -pub fn decodeCompactSize(v: []u8) DecodedCompactSize { - return switch (v[0]) { - 0...252 => DecodedCompactSize{ .totalBytes = 1, .n = v[0] }, - 253 => { - const n = std.mem.readInt(u16, v[1..3], .big); - return DecodedCompactSize{ .totalBytes = 3, .n = n }; - }, - 254 => { - const n = std.mem.readInt(u32, v[1..5], .big); - return DecodedCompactSize{ .totalBytes = 5, .n = n }; - }, - 255 => { - const n = std.mem.readInt(u64, v[1..9], .big); - return DecodedCompactSize{ .totalBytes = 9, .n = n }; - }, - }; -} - -pub fn encodeCompactSize(n: u64) EncodedCompactSize { - return switch (n) { - 0...252 => EncodedCompactSize{ .compactSizeByte = @intCast(n), .totalBytes = 0, .n = n }, - 253...65535 => EncodedCompactSize{ .compactSizeByte = 253, .totalBytes = 2, .n = n }, - 65536...4294967295 => EncodedCompactSize{ .compactSizeByte = 254, .totalBytes = 4, .n = n }, - 4294967296...18446744073709551615 => EncodedCompactSize{ .compactSizeByte = 255, .totalBytes = 8, .n = n }, - }; -} - -pub fn reverseByteOrderFromHex(comptime size: usize, hex: [size]u8) ![size]u8 { - var bytes: [size / 2]u8 = undefined; - const bytes_size = size / 2; - _ = try std.fmt.hexToBytes(&bytes, &hex); - - for (0..bytes_size / 2) |i| { // size / 4 = bytes.len / 2 - bytes[i] = bytes[bytes_size - 1 - i] ^ bytes[i]; - bytes[bytes_size - 1 - i] = bytes[i] ^ bytes[bytes_size - 1 - i]; - bytes[i] = bytes[bytes_size - 1 - i] ^ bytes[i]; - } - - var result: [size]u8 = undefined; - _ = try std.fmt.bufPrint(&result, "{x}", .{std.fmt.fmtSliceHexLower(&bytes)}); - return result; -} - -test "intToHexStr" { - var buffer: [8]u8 = undefined; - try intToHexStr(u8, 150, &buffer); - try std.testing.expectEqualSlices(u8, buffer[0..], "00000096"); - try intToHexStr(u32, 4294967295, &buffer); - try std.testing.expectEqualSlices(u8, buffer[0..], "ffffffff"); - - var buffer2: [8]u8 = undefined; - try intToHexStr(u8, 0, &buffer2); - try std.testing.expectEqualSlices(u8, buffer2[0..], "00000000"); -} - -test "toBase58" { - const str = "00f57f296d748bb310dc0512b28231e8ebd62454557d5edaef".*; - var b: [25]u8 = undefined; - _ = try std.fmt.hexToBytes(&b, &str); - var base58_address: [34]u8 = undefined; - _ = try toBase58(&base58_address, &b); - try std.testing.expectEqualSlices(u8, base58_address[0..], "1PP4tMi6tep8qo8NwUDRaNw5cdiDVZYEnJ"); -} - -test "hash160" { - var str = "03525cbe17e87969013e6457c765594580dc803a8497052d7c1efb0ef401f68bd5".*; - var bytes: [33]u8 = undefined; - _ = try std.fmt.hexToBytes(&bytes, &str); - const r = hash160(bytes[0..]); - var rstr: [40]u8 = undefined; - _ = try std.fmt.bufPrint(&rstr, "{x}", .{std.fmt.fmtSliceHexLower(&r)}); - try std.testing.expectEqualStrings("286fd267876fb1a24b8fe798edbc6dc6d5e2ea5b", &rstr); -} - -test "reverseByteOrderFromHex" { - const hex1 = "7790b18693b2c4b6344577dc8d973e51388670a2b60ef1156b69f141f66b837e".*; - const expected1 = "7e836bf641f1696b15f10eb6a2708638513e978ddc774534b6c4b29386b19077".*; - const res1 = try reverseByteOrderFromHex(64, hex1); - - const hex2 = "4429cda513e5258a16f5be9fe6bf9d8f18aa7d8ca6e5147b10961955db88ac74".*; - const expected2 = "74ac88db551996107b14e5a68c7daa188f9dbfe69fbef5168a25e513a5cd2944".*; - const res2 = try reverseByteOrderFromHex(64, hex2); - - const hex3 = "396b7f0fcac84f700b471fc72874f56795433b7cb7657fe3ff9e9d0e573960a7".*; - const expected3 = "a76039570e9d9effe37f65b77c3b439567f57428c71f470b704fc8ca0f7f6b39".*; - const res3 = try reverseByteOrderFromHex(64, hex3); - - try std.testing.expectEqualStrings(&expected1, &res1); - try std.testing.expectEqualStrings(&expected2, &res2); - try std.testing.expectEqualStrings(&expected3, &res3); -} diff --git a/src/core/bip39/bip39.zig b/src/core/bip39/bip39.zig deleted file mode 100644 index 005e4f7..0000000 --- a/src/core/bip39/bip39.zig +++ /dev/null @@ -1,352 +0,0 @@ -//! # BIP39 Mnemonic Codes -//! -//! https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki -//! -const std = @import("std"); -const language = @import("language.zig"); -const pbkdf2 = @import("pbkdf2.zig"); - -/// The minimum number of words in a mnemonic. -const MIN_NB_WORDS: usize = 12; - -/// The maximum number of words in a mnemonic. -const MAX_NB_WORDS: usize = 24; - -/// The index used to indicate the mnemonic ended. -const EOF: u16 = std.math.maxInt(u16); - -/// A mnemonic code. -/// -/// The [core::str::FromStr] implementation will try to determine the language of the -/// mnemonic from all the supported languages. (Languages have to be explicitly enabled using -/// the Cargo features.) -/// -/// Supported number of words are 12, 15, 18, 21, and 24. -pub const Mnemonic = struct { - /// The language the mnemonic. - lang: language.Language, - /// The indiced of the words. - /// Mnemonics with less than the max nb of words are terminated with EOF. - words: [MAX_NB_WORDS]u16, - - /// Parse a mnemonic in normalized UTF8 in the given language. - pub fn parseInNormalized(lang: language.Language, s: []const u8) !Mnemonic { - var it = std.mem.splitScalar(u8, s, ' '); - var nb_words: usize = 0; - - while (it.next()) |_| nb_words += 1; - it.reset(); - - if (isInvalidWordCount(nb_words)) { - return error.BadWordCount; - } - - // Here we will store the eventual words. - var words = [_]u16{EOF} ** MAX_NB_WORDS; - - // And here we keep track of the bits to calculate and validate the checksum. - // We only use `nb_words * 11` elements in this array. - var bits = [_]bool{false} ** (MAX_NB_WORDS * 11); - - { - var i: usize = 0; - while (it.next()) |word| { - const idx = lang.findWord(word) orelse return error.UnknownWord; - - words[i] = idx; - - for (0..11) |j| { - bits[i * 11 + j] = std.math.shr(u16, idx, 10 - j) & 1 == 1; - } - i += 1; - } - } - - // Verify the checksum. - // We only use `nb_words / 3 * 4` elements in this array. - - var entropy = [_]u8{0} ** (MAX_NB_WORDS / 3 * 4); - const nb_bytes_entropy = nb_words / 3 * 4; - for (0..nb_bytes_entropy) |i| { - for (0..8) |j| { - if (bits[i * 8 + j]) { - entropy[i] += std.math.shl(u8, 1, 7 - j); - } - } - } - - var hasher = std.crypto.hash.sha2.Sha256.init(.{}); - hasher.update(entropy[0..nb_bytes_entropy]); - - const check = hasher.finalResult(); - - for (0..nb_bytes_entropy / 4) |i| { - if (bits[8 * nb_bytes_entropy + i] != ((check[i / 8] & (std.math.shl(usize, 1, 7 - (i % 8)))) > 0)) { - return error.InvalidChecksum; - } - } - - return .{ - .lang = lang, - .words = words, - }; - } - - /// Convert to seed bytes with a passphrase in normalized UTF8. - pub fn toSeedNormalized(self: Mnemonic, normalized_passphrase: []const u8) ![64]u8 { - const PBKDF2_ROUNDS: usize = 2048; - const PBKDF2_BYTES: usize = 64; - - var seed = [_]u8{0} ** PBKDF2_BYTES; - - pbkdf2.pbkdf2((try self.getWords()).slice(), normalized_passphrase, PBKDF2_ROUNDS, &seed); - return seed; - } - - /// Returns an slice over [Mnemonic] word indices. - /// - pub fn wordIndices(self: Mnemonic) !std.BoundedArray(u16, MAX_NB_WORDS) { - var result = try std.BoundedArray(u16, MAX_NB_WORDS).init(0); - - for (self.words) |w| { - if (w != EOF) { - result.appendAssumeCapacity(w); - continue; - } - - break; - } - - return result; - } - - /// Returns an iterator over the words of the [Mnemonic]. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// const bip39 = @import("bip39"); - /// - /// const mnemonic = try bip39.Mnemonic.fromEntropy(&([_]u8{0} ** 32)); - /// for (mnemonic.words()) |word| { - /// std.log.debug("word: {s}", .{word}); - /// } - /// ``` - pub fn getWords(self: Mnemonic) !std.BoundedArray([]const u8, MAX_NB_WORDS) { - const list = self.lang.wordList(); - const word_indices = try self.wordIndices(); - - var result = try std.BoundedArray([]const u8, MAX_NB_WORDS).init(0); - - for (word_indices.slice()) |i| { - result.appendAssumeCapacity(list[i]); - } - - return result; - } - - /// Create a new [Mnemonic] in the specified language from the given entropy. - /// Entropy must be a multiple of 32 bits (4 bytes) and 128-256 bits in length. - pub fn fromEntropyIn(lang: language.Language, entropy: []const u8) !Mnemonic { - const MAX_ENTROPY_BITS: usize = 256; - const MIN_ENTROPY_BITS: usize = 128; - const MAX_CHECKSUM_BITS: usize = 8; - - const nb_bytes = entropy.len; - const nb_bits = nb_bytes * 8; - - if (nb_bits % 32 != 0) { - return error.BadEntropyBitCount; - } - - if (nb_bits < MIN_ENTROPY_BITS or nb_bits > MAX_ENTROPY_BITS) { - return error.BadEntropyBitCount; - } - - const check = v: { - var out: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; - std.crypto.hash.sha2.Sha256.hash(entropy, &out, .{}); - break :v out; - }; - - var bits = [_]bool{false} ** (MAX_ENTROPY_BITS + MAX_CHECKSUM_BITS); - - for (0..nb_bytes) |i| { - for (0..8) |j| { - bits[i * 8 + j] = (entropy[i] & (std.math.shl(usize, 1, 7 - j))) > 0; - } - } - - for (0..nb_bytes / 4) |i| { - bits[8 * nb_bytes + i] = (check[i / 8] & (std.math.shl(usize, 1, 7 - (i % 8)))) > 0; - } - - var words = [_]u16{EOF} ** MAX_NB_WORDS; - const nb_words = nb_bytes * 3 / 4; - for (0..nb_words) |i| { - var idx: u16 = 0; - for (0..11) |j| { - if (bits[i * 11 + j]) { - idx += std.math.shl(u16, 1, @as(u16, @truncate(10 - j))); - } - } - - words[i] = idx; - } - - return .{ - .lang = lang, - .words = words, - }; - } -}; - -fn isInvalidWordCount(word_count: usize) bool { - return word_count < MIN_NB_WORDS or word_count % 3 != 0 or word_count > MAX_NB_WORDS; -} - -test "english_vectors" { - // These vectors are tuples of - // (entropy, mnemonic, seed) - - const test_vectors = [_]struct { []const u8, []const u8, []const u8 }{ - .{ - "00000000000000000000000000000000", - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", - }, - .{ - "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - "legal winner thank year wave sausage worth useful legal winner thank yellow", - "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", - }, - .{ - "80808080808080808080808080808080", - "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", - "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", - }, - .{ - "ffffffffffffffffffffffffffffffff", - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", - "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", - }, - .{ - "000000000000000000000000000000000000000000000000", - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", - "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", - }, - .{ - "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", - "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", - }, - .{ - "808080808080808080808080808080808080808080808080", - "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", - "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", - }, - .{ - "ffffffffffffffffffffffffffffffffffffffffffffffff", - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", - "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", - }, - .{ - "0000000000000000000000000000000000000000000000000000000000000000", - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", - "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", - }, - .{ - "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", - "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", - }, - .{ - "8080808080808080808080808080808080808080808080808080808080808080", - "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", - "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", - }, - .{ - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", - "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", - }, - .{ - "9e885d952ad362caeb4efe34a8e91bd2", - "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", - "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028", - }, - .{ - "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", - "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", - "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac", - }, - .{ - "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", - "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", - "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440", - }, - .{ - "c0ba5a8e914111210f2bd131f3d5e08d", - "scheme spot photo card baby mountain device kick cradle pact join borrow", - "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612", - }, - .{ - "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", - "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", - "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d", - }, - .{ - "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", - "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", - "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d", - }, - .{ - "23db8160a31d3e0dca3688ed941adbf3", - "cat swing flag economy stadium alone churn speed unique patch report train", - "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5", - }, - .{ - "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", - "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", - "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02", - }, - .{ - "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", - "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", - "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d", - }, - .{ - "f30f8c1da665478f49b001d94c5fc452", - "vessel ladder alter error federal sibling chat ability sun glass valve picture", - "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f", - }, - .{ - "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", - "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", - "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88", - }, - .{ - "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", - "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", - "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998", - }, - }; - - var buf: [300]u8 = undefined; - - for (test_vectors) |vector| { - const entropy = try std.fmt.hexToBytes(&buf, vector[0]); - const mn = try Mnemonic.fromEntropyIn(.english, entropy); - const mn1 = try Mnemonic.parseInNormalized(.english, vector[1]); - - try std.testing.expectEqualDeep(mn, mn1); - - const seed = try std.fmt.hexToBytes(buf[100..], vector[2]); - - const seeded = try mn.toSeedNormalized("TREZOR"); - - try std.testing.expectEqualSlices(u8, &seeded, seed); - } -} diff --git a/src/core/bip39/language.zig b/src/core/bip39/language.zig deleted file mode 100644 index 22ac827..0000000 --- a/src/core/bip39/language.zig +++ /dev/null @@ -1,2124 +0,0 @@ -const std = @import("std"); - -/// The maximum number of languages enabled. -pub const MAX_NB_LANGUAGES: usize = 9; - -pub const Language = enum { - /// The English language. - english, - - pub inline fn wordList(self: Language) *const [2048][]const u8 { - return switch (self) { - inline .english => return &ENGLISH_WORDS, - }; - } - - /// Returns true if all words in the list are guaranteed to - /// only be in this list and not in any other. - pub inline fn uniqueWords(self: Language) bool { - return switch (self) { - inline .english => false, - }; - } - - /// Get words from the word list that start with the given prefix. - pub fn wordsByPrefix(self: Language, prefix: []const u8) ?[]const []const u8 { - // The words in the word list are ordered lexicographically. This means - // that we cannot use `binary_search` to find words more efficiently, - // because the Rust ordering is based on the byte values. However, it - // does mean that words that share a prefix will follow each other. - var start_from: usize = 0; - var count: usize = 0; - - const word_list = self.wordList(); - for (word_list.*, 0..) |w, idx| { - if (std.mem.startsWith(u8, w, prefix)) { - count = 1; - start_from = idx; - - for (idx + 1..2048) |i| { - if (!std.mem.startsWith(u8, word_list.*[i], prefix)) break; - - count += 1; - } - break; - } - } - - if (count == 0) return null; - - return word_list[start_from .. start_from + count]; - } - - /// Get the index of the word in the word list. - pub inline fn findWord(self: Language, word: []const u8) ?u16 { - for (self.wordList(), 0..) |w, i| { - if (std.mem.eql(u8, w, word)) return @truncate(i); - } - - return null; - } -}; - -const ENGLISH_WORDS: [2048][]const u8 = .{ - "abandon", - "ability", - "able", - "about", - "above", - "absent", - "absorb", - "abstract", - "absurd", - "abuse", - "access", - "accident", - "account", - "accuse", - "achieve", - "acid", - "acoustic", - "acquire", - "across", - "act", - "action", - "actor", - "actress", - "actual", - "adapt", - "add", - "addict", - "address", - "adjust", - "admit", - "adult", - "advance", - "advice", - "aerobic", - "affair", - "afford", - "afraid", - "again", - "age", - "agent", - "agree", - "ahead", - "aim", - "air", - "airport", - "aisle", - "alarm", - "album", - "alcohol", - "alert", - "alien", - "all", - "alley", - "allow", - "almost", - "alone", - "alpha", - "already", - "also", - "alter", - "always", - "amateur", - "amazing", - "among", - "amount", - "amused", - "analyst", - "anchor", - "ancient", - "anger", - "angle", - "angry", - "animal", - "ankle", - "announce", - "annual", - "another", - "answer", - "antenna", - "antique", - "anxiety", - "any", - "apart", - "apology", - "appear", - "apple", - "approve", - "april", - "arch", - "arctic", - "area", - "arena", - "argue", - "arm", - "armed", - "armor", - "army", - "around", - "arrange", - "arrest", - "arrive", - "arrow", - "art", - "artefact", - "artist", - "artwork", - "ask", - "aspect", - "assault", - "asset", - "assist", - "assume", - "asthma", - "athlete", - "atom", - "attack", - "attend", - "attitude", - "attract", - "auction", - "audit", - "august", - "aunt", - "author", - "auto", - "autumn", - "average", - "avocado", - "avoid", - "awake", - "aware", - "away", - "awesome", - "awful", - "awkward", - "axis", - "baby", - "bachelor", - "bacon", - "badge", - "bag", - "balance", - "balcony", - "ball", - "bamboo", - "banana", - "banner", - "bar", - "barely", - "bargain", - "barrel", - "base", - "basic", - "basket", - "battle", - "beach", - "bean", - "beauty", - "because", - "become", - "beef", - "before", - "begin", - "behave", - "behind", - "believe", - "below", - "belt", - "bench", - "benefit", - "best", - "betray", - "better", - "between", - "beyond", - "bicycle", - "bid", - "bike", - "bind", - "biology", - "bird", - "birth", - "bitter", - "black", - "blade", - "blame", - "blanket", - "blast", - "bleak", - "bless", - "blind", - "blood", - "blossom", - "blouse", - "blue", - "blur", - "blush", - "board", - "boat", - "body", - "boil", - "bomb", - "bone", - "bonus", - "book", - "boost", - "border", - "boring", - "borrow", - "boss", - "bottom", - "bounce", - "box", - "boy", - "bracket", - "brain", - "brand", - "brass", - "brave", - "bread", - "breeze", - "brick", - "bridge", - "brief", - "bright", - "bring", - "brisk", - "broccoli", - "broken", - "bronze", - "broom", - "brother", - "brown", - "brush", - "bubble", - "buddy", - "budget", - "buffalo", - "build", - "bulb", - "bulk", - "bullet", - "bundle", - "bunker", - "burden", - "burger", - "burst", - "bus", - "business", - "busy", - "butter", - "buyer", - "buzz", - "cabbage", - "cabin", - "cable", - "cactus", - "cage", - "cake", - "call", - "calm", - "camera", - "camp", - "can", - "canal", - "cancel", - "candy", - "cannon", - "canoe", - "canvas", - "canyon", - "capable", - "capital", - "captain", - "car", - "carbon", - "card", - "cargo", - "carpet", - "carry", - "cart", - "case", - "cash", - "casino", - "castle", - "casual", - "cat", - "catalog", - "catch", - "category", - "cattle", - "caught", - "cause", - "caution", - "cave", - "ceiling", - "celery", - "cement", - "census", - "century", - "cereal", - "certain", - "chair", - "chalk", - "champion", - "change", - "chaos", - "chapter", - "charge", - "chase", - "chat", - "cheap", - "check", - "cheese", - "chef", - "cherry", - "chest", - "chicken", - "chief", - "child", - "chimney", - "choice", - "choose", - "chronic", - "chuckle", - "chunk", - "churn", - "cigar", - "cinnamon", - "circle", - "citizen", - "city", - "civil", - "claim", - "clap", - "clarify", - "claw", - "clay", - "clean", - "clerk", - "clever", - "click", - "client", - "cliff", - "climb", - "clinic", - "clip", - "clock", - "clog", - "close", - "cloth", - "cloud", - "clown", - "club", - "clump", - "cluster", - "clutch", - "coach", - "coast", - "coconut", - "code", - "coffee", - "coil", - "coin", - "collect", - "color", - "column", - "combine", - "come", - "comfort", - "comic", - "common", - "company", - "concert", - "conduct", - "confirm", - "congress", - "connect", - "consider", - "control", - "convince", - "cook", - "cool", - "copper", - "copy", - "coral", - "core", - "corn", - "correct", - "cost", - "cotton", - "couch", - "country", - "couple", - "course", - "cousin", - "cover", - "coyote", - "crack", - "cradle", - "craft", - "cram", - "crane", - "crash", - "crater", - "crawl", - "crazy", - "cream", - "credit", - "creek", - "crew", - "cricket", - "crime", - "crisp", - "critic", - "crop", - "cross", - "crouch", - "crowd", - "crucial", - "cruel", - "cruise", - "crumble", - "crunch", - "crush", - "cry", - "crystal", - "cube", - "culture", - "cup", - "cupboard", - "curious", - "current", - "curtain", - "curve", - "cushion", - "custom", - "cute", - "cycle", - "dad", - "damage", - "damp", - "dance", - "danger", - "daring", - "dash", - "daughter", - "dawn", - "day", - "deal", - "debate", - "debris", - "decade", - "december", - "decide", - "decline", - "decorate", - "decrease", - "deer", - "defense", - "define", - "defy", - "degree", - "delay", - "deliver", - "demand", - "demise", - "denial", - "dentist", - "deny", - "depart", - "depend", - "deposit", - "depth", - "deputy", - "derive", - "describe", - "desert", - "design", - "desk", - "despair", - "destroy", - "detail", - "detect", - "develop", - "device", - "devote", - "diagram", - "dial", - "diamond", - "diary", - "dice", - "diesel", - "diet", - "differ", - "digital", - "dignity", - "dilemma", - "dinner", - "dinosaur", - "direct", - "dirt", - "disagree", - "discover", - "disease", - "dish", - "dismiss", - "disorder", - "display", - "distance", - "divert", - "divide", - "divorce", - "dizzy", - "doctor", - "document", - "dog", - "doll", - "dolphin", - "domain", - "donate", - "donkey", - "donor", - "door", - "dose", - "double", - "dove", - "draft", - "dragon", - "drama", - "drastic", - "draw", - "dream", - "dress", - "drift", - "drill", - "drink", - "drip", - "drive", - "drop", - "drum", - "dry", - "duck", - "dumb", - "dune", - "during", - "dust", - "dutch", - "duty", - "dwarf", - "dynamic", - "eager", - "eagle", - "early", - "earn", - "earth", - "easily", - "east", - "easy", - "echo", - "ecology", - "economy", - "edge", - "edit", - "educate", - "effort", - "egg", - "eight", - "either", - "elbow", - "elder", - "electric", - "elegant", - "element", - "elephant", - "elevator", - "elite", - "else", - "embark", - "embody", - "embrace", - "emerge", - "emotion", - "employ", - "empower", - "empty", - "enable", - "enact", - "end", - "endless", - "endorse", - "enemy", - "energy", - "enforce", - "engage", - "engine", - "enhance", - "enjoy", - "enlist", - "enough", - "enrich", - "enroll", - "ensure", - "enter", - "entire", - "entry", - "envelope", - "episode", - "equal", - "equip", - "era", - "erase", - "erode", - "erosion", - "error", - "erupt", - "escape", - "essay", - "essence", - "estate", - "eternal", - "ethics", - "evidence", - "evil", - "evoke", - "evolve", - "exact", - "example", - "excess", - "exchange", - "excite", - "exclude", - "excuse", - "execute", - "exercise", - "exhaust", - "exhibit", - "exile", - "exist", - "exit", - "exotic", - "expand", - "expect", - "expire", - "explain", - "expose", - "express", - "extend", - "extra", - "eye", - "eyebrow", - "fabric", - "face", - "faculty", - "fade", - "faint", - "faith", - "fall", - "false", - "fame", - "family", - "famous", - "fan", - "fancy", - "fantasy", - "farm", - "fashion", - "fat", - "fatal", - "father", - "fatigue", - "fault", - "favorite", - "feature", - "february", - "federal", - "fee", - "feed", - "feel", - "female", - "fence", - "festival", - "fetch", - "fever", - "few", - "fiber", - "fiction", - "field", - "figure", - "file", - "film", - "filter", - "final", - "find", - "fine", - "finger", - "finish", - "fire", - "firm", - "first", - "fiscal", - "fish", - "fit", - "fitness", - "fix", - "flag", - "flame", - "flash", - "flat", - "flavor", - "flee", - "flight", - "flip", - "float", - "flock", - "floor", - "flower", - "fluid", - "flush", - "fly", - "foam", - "focus", - "fog", - "foil", - "fold", - "follow", - "food", - "foot", - "force", - "forest", - "forget", - "fork", - "fortune", - "forum", - "forward", - "fossil", - "foster", - "found", - "fox", - "fragile", - "frame", - "frequent", - "fresh", - "friend", - "fringe", - "frog", - "front", - "frost", - "frown", - "frozen", - "fruit", - "fuel", - "fun", - "funny", - "furnace", - "fury", - "future", - "gadget", - "gain", - "galaxy", - "gallery", - "game", - "gap", - "garage", - "garbage", - "garden", - "garlic", - "garment", - "gas", - "gasp", - "gate", - "gather", - "gauge", - "gaze", - "general", - "genius", - "genre", - "gentle", - "genuine", - "gesture", - "ghost", - "giant", - "gift", - "giggle", - "ginger", - "giraffe", - "girl", - "give", - "glad", - "glance", - "glare", - "glass", - "glide", - "glimpse", - "globe", - "gloom", - "glory", - "glove", - "glow", - "glue", - "goat", - "goddess", - "gold", - "good", - "goose", - "gorilla", - "gospel", - "gossip", - "govern", - "gown", - "grab", - "grace", - "grain", - "grant", - "grape", - "grass", - "gravity", - "great", - "green", - "grid", - "grief", - "grit", - "grocery", - "group", - "grow", - "grunt", - "guard", - "guess", - "guide", - "guilt", - "guitar", - "gun", - "gym", - "habit", - "hair", - "half", - "hammer", - "hamster", - "hand", - "happy", - "harbor", - "hard", - "harsh", - "harvest", - "hat", - "have", - "hawk", - "hazard", - "head", - "health", - "heart", - "heavy", - "hedgehog", - "height", - "hello", - "helmet", - "help", - "hen", - "hero", - "hidden", - "high", - "hill", - "hint", - "hip", - "hire", - "history", - "hobby", - "hockey", - "hold", - "hole", - "holiday", - "hollow", - "home", - "honey", - "hood", - "hope", - "horn", - "horror", - "horse", - "hospital", - "host", - "hotel", - "hour", - "hover", - "hub", - "huge", - "human", - "humble", - "humor", - "hundred", - "hungry", - "hunt", - "hurdle", - "hurry", - "hurt", - "husband", - "hybrid", - "ice", - "icon", - "idea", - "identify", - "idle", - "ignore", - "ill", - "illegal", - "illness", - "image", - "imitate", - "immense", - "immune", - "impact", - "impose", - "improve", - "impulse", - "inch", - "include", - "income", - "increase", - "index", - "indicate", - "indoor", - "industry", - "infant", - "inflict", - "inform", - "inhale", - "inherit", - "initial", - "inject", - "injury", - "inmate", - "inner", - "innocent", - "input", - "inquiry", - "insane", - "insect", - "inside", - "inspire", - "install", - "intact", - "interest", - "into", - "invest", - "invite", - "involve", - "iron", - "island", - "isolate", - "issue", - "item", - "ivory", - "jacket", - "jaguar", - "jar", - "jazz", - "jealous", - "jeans", - "jelly", - "jewel", - "job", - "join", - "joke", - "journey", - "joy", - "judge", - "juice", - "jump", - "jungle", - "junior", - "junk", - "just", - "kangaroo", - "keen", - "keep", - "ketchup", - "key", - "kick", - "kid", - "kidney", - "kind", - "kingdom", - "kiss", - "kit", - "kitchen", - "kite", - "kitten", - "kiwi", - "knee", - "knife", - "knock", - "know", - "lab", - "label", - "labor", - "ladder", - "lady", - "lake", - "lamp", - "language", - "laptop", - "large", - "later", - "latin", - "laugh", - "laundry", - "lava", - "law", - "lawn", - "lawsuit", - "layer", - "lazy", - "leader", - "leaf", - "learn", - "leave", - "lecture", - "left", - "leg", - "legal", - "legend", - "leisure", - "lemon", - "lend", - "length", - "lens", - "leopard", - "lesson", - "letter", - "level", - "liar", - "liberty", - "library", - "license", - "life", - "lift", - "light", - "like", - "limb", - "limit", - "link", - "lion", - "liquid", - "list", - "little", - "live", - "lizard", - "load", - "loan", - "lobster", - "local", - "lock", - "logic", - "lonely", - "long", - "loop", - "lottery", - "loud", - "lounge", - "love", - "loyal", - "lucky", - "luggage", - "lumber", - "lunar", - "lunch", - "luxury", - "lyrics", - "machine", - "mad", - "magic", - "magnet", - "maid", - "mail", - "main", - "major", - "make", - "mammal", - "man", - "manage", - "mandate", - "mango", - "mansion", - "manual", - "maple", - "marble", - "march", - "margin", - "marine", - "market", - "marriage", - "mask", - "mass", - "master", - "match", - "material", - "math", - "matrix", - "matter", - "maximum", - "maze", - "meadow", - "mean", - "measure", - "meat", - "mechanic", - "medal", - "media", - "melody", - "melt", - "member", - "memory", - "mention", - "menu", - "mercy", - "merge", - "merit", - "merry", - "mesh", - "message", - "metal", - "method", - "middle", - "midnight", - "milk", - "million", - "mimic", - "mind", - "minimum", - "minor", - "minute", - "miracle", - "mirror", - "misery", - "miss", - "mistake", - "mix", - "mixed", - "mixture", - "mobile", - "model", - "modify", - "mom", - "moment", - "monitor", - "monkey", - "monster", - "month", - "moon", - "moral", - "more", - "morning", - "mosquito", - "mother", - "motion", - "motor", - "mountain", - "mouse", - "move", - "movie", - "much", - "muffin", - "mule", - "multiply", - "muscle", - "museum", - "mushroom", - "music", - "must", - "mutual", - "myself", - "mystery", - "myth", - "naive", - "name", - "napkin", - "narrow", - "nasty", - "nation", - "nature", - "near", - "neck", - "need", - "negative", - "neglect", - "neither", - "nephew", - "nerve", - "nest", - "net", - "network", - "neutral", - "never", - "news", - "next", - "nice", - "night", - "noble", - "noise", - "nominee", - "noodle", - "normal", - "north", - "nose", - "notable", - "note", - "nothing", - "notice", - "novel", - "now", - "nuclear", - "number", - "nurse", - "nut", - "oak", - "obey", - "object", - "oblige", - "obscure", - "observe", - "obtain", - "obvious", - "occur", - "ocean", - "october", - "odor", - "off", - "offer", - "office", - "often", - "oil", - "okay", - "old", - "olive", - "olympic", - "omit", - "once", - "one", - "onion", - "online", - "only", - "open", - "opera", - "opinion", - "oppose", - "option", - "orange", - "orbit", - "orchard", - "order", - "ordinary", - "organ", - "orient", - "original", - "orphan", - "ostrich", - "other", - "outdoor", - "outer", - "output", - "outside", - "oval", - "oven", - "over", - "own", - "owner", - "oxygen", - "oyster", - "ozone", - "pact", - "paddle", - "page", - "pair", - "palace", - "palm", - "panda", - "panel", - "panic", - "panther", - "paper", - "parade", - "parent", - "park", - "parrot", - "party", - "pass", - "patch", - "path", - "patient", - "patrol", - "pattern", - "pause", - "pave", - "payment", - "peace", - "peanut", - "pear", - "peasant", - "pelican", - "pen", - "penalty", - "pencil", - "people", - "pepper", - "perfect", - "permit", - "person", - "pet", - "phone", - "photo", - "phrase", - "physical", - "piano", - "picnic", - "picture", - "piece", - "pig", - "pigeon", - "pill", - "pilot", - "pink", - "pioneer", - "pipe", - "pistol", - "pitch", - "pizza", - "place", - "planet", - "plastic", - "plate", - "play", - "please", - "pledge", - "pluck", - "plug", - "plunge", - "poem", - "poet", - "point", - "polar", - "pole", - "police", - "pond", - "pony", - "pool", - "popular", - "portion", - "position", - "possible", - "post", - "potato", - "pottery", - "poverty", - "powder", - "power", - "practice", - "praise", - "predict", - "prefer", - "prepare", - "present", - "pretty", - "prevent", - "price", - "pride", - "primary", - "print", - "priority", - "prison", - "private", - "prize", - "problem", - "process", - "produce", - "profit", - "program", - "project", - "promote", - "proof", - "property", - "prosper", - "protect", - "proud", - "provide", - "public", - "pudding", - "pull", - "pulp", - "pulse", - "pumpkin", - "punch", - "pupil", - "puppy", - "purchase", - "purity", - "purpose", - "purse", - "push", - "put", - "puzzle", - "pyramid", - "quality", - "quantum", - "quarter", - "question", - "quick", - "quit", - "quiz", - "quote", - "rabbit", - "raccoon", - "race", - "rack", - "radar", - "radio", - "rail", - "rain", - "raise", - "rally", - "ramp", - "ranch", - "random", - "range", - "rapid", - "rare", - "rate", - "rather", - "raven", - "raw", - "razor", - "ready", - "real", - "reason", - "rebel", - "rebuild", - "recall", - "receive", - "recipe", - "record", - "recycle", - "reduce", - "reflect", - "reform", - "refuse", - "region", - "regret", - "regular", - "reject", - "relax", - "release", - "relief", - "rely", - "remain", - "remember", - "remind", - "remove", - "render", - "renew", - "rent", - "reopen", - "repair", - "repeat", - "replace", - "report", - "require", - "rescue", - "resemble", - "resist", - "resource", - "response", - "result", - "retire", - "retreat", - "return", - "reunion", - "reveal", - "review", - "reward", - "rhythm", - "rib", - "ribbon", - "rice", - "rich", - "ride", - "ridge", - "rifle", - "right", - "rigid", - "ring", - "riot", - "ripple", - "risk", - "ritual", - "rival", - "river", - "road", - "roast", - "robot", - "robust", - "rocket", - "romance", - "roof", - "rookie", - "room", - "rose", - "rotate", - "rough", - "round", - "route", - "royal", - "rubber", - "rude", - "rug", - "rule", - "run", - "runway", - "rural", - "sad", - "saddle", - "sadness", - "safe", - "sail", - "salad", - "salmon", - "salon", - "salt", - "salute", - "same", - "sample", - "sand", - "satisfy", - "satoshi", - "sauce", - "sausage", - "save", - "say", - "scale", - "scan", - "scare", - "scatter", - "scene", - "scheme", - "school", - "science", - "scissors", - "scorpion", - "scout", - "scrap", - "screen", - "script", - "scrub", - "sea", - "search", - "season", - "seat", - "second", - "secret", - "section", - "security", - "seed", - "seek", - "segment", - "select", - "sell", - "seminar", - "senior", - "sense", - "sentence", - "series", - "service", - "session", - "settle", - "setup", - "seven", - "shadow", - "shaft", - "shallow", - "share", - "shed", - "shell", - "sheriff", - "shield", - "shift", - "shine", - "ship", - "shiver", - "shock", - "shoe", - "shoot", - "shop", - "short", - "shoulder", - "shove", - "shrimp", - "shrug", - "shuffle", - "shy", - "sibling", - "sick", - "side", - "siege", - "sight", - "sign", - "silent", - "silk", - "silly", - "silver", - "similar", - "simple", - "since", - "sing", - "siren", - "sister", - "situate", - "six", - "size", - "skate", - "sketch", - "ski", - "skill", - "skin", - "skirt", - "skull", - "slab", - "slam", - "sleep", - "slender", - "slice", - "slide", - "slight", - "slim", - "slogan", - "slot", - "slow", - "slush", - "small", - "smart", - "smile", - "smoke", - "smooth", - "snack", - "snake", - "snap", - "sniff", - "snow", - "soap", - "soccer", - "social", - "sock", - "soda", - "soft", - "solar", - "soldier", - "solid", - "solution", - "solve", - "someone", - "song", - "soon", - "sorry", - "sort", - "soul", - "sound", - "soup", - "source", - "south", - "space", - "spare", - "spatial", - "spawn", - "speak", - "special", - "speed", - "spell", - "spend", - "sphere", - "spice", - "spider", - "spike", - "spin", - "spirit", - "split", - "spoil", - "sponsor", - "spoon", - "sport", - "spot", - "spray", - "spread", - "spring", - "spy", - "square", - "squeeze", - "squirrel", - "stable", - "stadium", - "staff", - "stage", - "stairs", - "stamp", - "stand", - "start", - "state", - "stay", - "steak", - "steel", - "stem", - "step", - "stereo", - "stick", - "still", - "sting", - "stock", - "stomach", - "stone", - "stool", - "story", - "stove", - "strategy", - "street", - "strike", - "strong", - "struggle", - "student", - "stuff", - "stumble", - "style", - "subject", - "submit", - "subway", - "success", - "such", - "sudden", - "suffer", - "sugar", - "suggest", - "suit", - "summer", - "sun", - "sunny", - "sunset", - "super", - "supply", - "supreme", - "sure", - "surface", - "surge", - "surprise", - "surround", - "survey", - "suspect", - "sustain", - "swallow", - "swamp", - "swap", - "swarm", - "swear", - "sweet", - "swift", - "swim", - "swing", - "switch", - "sword", - "symbol", - "symptom", - "syrup", - "system", - "table", - "tackle", - "tag", - "tail", - "talent", - "talk", - "tank", - "tape", - "target", - "task", - "taste", - "tattoo", - "taxi", - "teach", - "team", - "tell", - "ten", - "tenant", - "tennis", - "tent", - "term", - "test", - "text", - "thank", - "that", - "theme", - "then", - "theory", - "there", - "they", - "thing", - "this", - "thought", - "three", - "thrive", - "throw", - "thumb", - "thunder", - "ticket", - "tide", - "tiger", - "tilt", - "timber", - "time", - "tiny", - "tip", - "tired", - "tissue", - "title", - "toast", - "tobacco", - "today", - "toddler", - "toe", - "together", - "toilet", - "token", - "tomato", - "tomorrow", - "tone", - "tongue", - "tonight", - "tool", - "tooth", - "top", - "topic", - "topple", - "torch", - "tornado", - "tortoise", - "toss", - "total", - "tourist", - "toward", - "tower", - "town", - "toy", - "track", - "trade", - "traffic", - "tragic", - "train", - "transfer", - "trap", - "trash", - "travel", - "tray", - "treat", - "tree", - "trend", - "trial", - "tribe", - "trick", - "trigger", - "trim", - "trip", - "trophy", - "trouble", - "truck", - "true", - "truly", - "trumpet", - "trust", - "truth", - "try", - "tube", - "tuition", - "tumble", - "tuna", - "tunnel", - "turkey", - "turn", - "turtle", - "twelve", - "twenty", - "twice", - "twin", - "twist", - "two", - "type", - "typical", - "ugly", - "umbrella", - "unable", - "unaware", - "uncle", - "uncover", - "under", - "undo", - "unfair", - "unfold", - "unhappy", - "uniform", - "unique", - "unit", - "universe", - "unknown", - "unlock", - "until", - "unusual", - "unveil", - "update", - "upgrade", - "uphold", - "upon", - "upper", - "upset", - "urban", - "urge", - "usage", - "use", - "used", - "useful", - "useless", - "usual", - "utility", - "vacant", - "vacuum", - "vague", - "valid", - "valley", - "valve", - "van", - "vanish", - "vapor", - "various", - "vast", - "vault", - "vehicle", - "velvet", - "vendor", - "venture", - "venue", - "verb", - "verify", - "version", - "very", - "vessel", - "veteran", - "viable", - "vibrant", - "vicious", - "victory", - "video", - "view", - "village", - "vintage", - "violin", - "virtual", - "virus", - "visa", - "visit", - "visual", - "vital", - "vivid", - "vocal", - "voice", - "void", - "volcano", - "volume", - "vote", - "voyage", - "wage", - "wagon", - "wait", - "walk", - "wall", - "walnut", - "want", - "warfare", - "warm", - "warrior", - "wash", - "wasp", - "waste", - "water", - "wave", - "way", - "wealth", - "weapon", - "wear", - "weasel", - "weather", - "web", - "wedding", - "weekend", - "weird", - "welcome", - "west", - "wet", - "whale", - "what", - "wheat", - "wheel", - "when", - "where", - "whip", - "whisper", - "wide", - "width", - "wife", - "wild", - "will", - "win", - "window", - "wine", - "wing", - "wink", - "winner", - "winter", - "wire", - "wisdom", - "wise", - "wish", - "witness", - "wolf", - "woman", - "wonder", - "wood", - "wool", - "word", - "work", - "world", - "worry", - "worth", - "wrap", - "wreck", - "wrestle", - "wrist", - "write", - "wrong", - "yard", - "year", - "yellow", - "you", - "young", - "youth", - "zebra", - "zero", - "zone", - "zoo", -}; - -test "words_by_prefix" { - const lang: Language = .english; - - var res = lang.wordsByPrefix("woo") orelse @panic("not expect"); - try std.testing.expectEqualSlices([]const u8, &.{ "wood", "wool" }, res); - - res = lang.wordsByPrefix("") orelse @panic("not expect"); - try std.testing.expectEqual(res.len, 2048); - - try std.testing.expect(lang.wordsByPrefix("woof") == null); -} diff --git a/src/core/bip39/pbkdf2.zig b/src/core/bip39/pbkdf2.zig deleted file mode 100644 index 5f773f3..0000000 --- a/src/core/bip39/pbkdf2.zig +++ /dev/null @@ -1,143 +0,0 @@ -const std = @import("std"); -const SALT_PREFIX = "mnemonic"; - -const Hmac = std.crypto.auth.hmac.sha2.HmacSha512; -const Sha512 = std.crypto.hash.sha2.Sha512; - -/// Calculate the binary size of the mnemonic. -fn mnemonicByteLen(mnemonic: []const []const u8) usize { - var len: usize = 0; - for (0.., mnemonic) |i, word| { - if (i > 0) { - len += 1; - } - - len += word.len; - } - return len; -} - -/// Wrote the mnemonic in binary form into the hash engine. -fn mnemonicWriteInto(mnemonic: []const []const u8, engine: *Sha512) void { - for (0.., mnemonic) |i, word| { - if (i > 0) { - engine.update(" "); - } - engine.update(word); - } -} - -/// Create an HMAC engine from the passphrase. -/// We need a special method because we can't allocate a new byte -/// vector for the entire serialized mnemonic. -fn createHmacEngine(mnemonic: []const []const u8) Hmac { - // Inner code is borrowed from the bitcoin_hashes::hmac::HmacEngine::new method. - var ipad = [_]u8{0x36} ** 128; - var opad = [_]u8{0x5c} ** 128; - - var iengine = Sha512.init(.{}); - - if (mnemonicByteLen(mnemonic) > Sha512.block_length) { - const hash = v: { - var engine = Sha512.init(.{}); - mnemonicWriteInto(mnemonic, &engine); - var final: [Sha512.digest_length]u8 = undefined; - engine.final(&final); - break :v final; - }; - - for (ipad[0..64], hash) |*b_i, b_h| { - b_i.* = b_i.* ^ b_h; - } - - for (opad[0..64], hash) |*b_o, b_h| { - b_o.* = b_o.* ^ b_h; - } - } else { - // First modify the first elements from the prefix. - var cursor: usize = 0; - for (0.., mnemonic) |i, word| { - if (i > 0) { - ipad[cursor] ^= ' '; - opad[cursor] ^= ' '; - cursor += 1; - } - - const min_len = @min(ipad.len - cursor, word.len); - for (ipad[cursor .. cursor + min_len], word[0..min_len]) |*b_i, b_h| { - b_i.* = b_i.* ^ b_h; - } - - for (opad[cursor .. cursor + min_len], word[0..min_len]) |*b_o, b_h| { - b_o.* = b_o.* ^ b_h; - } - - cursor += word.len; - // assert!(cursor <= sha512::HashEngine::BLOCK_SIZE, "mnemonic_byte_len is broken"); - } - } - - iengine.update(ipad[0..Sha512.block_length]); - - return Hmac{ - .o_key_pad = opad[0..Sha512.block_length].*, - .hash = iengine, - }; -} - -inline fn xor(res: []u8, salt: []const u8) void { - // length mismatch in xor - std.debug.assert(salt.len >= res.len); - const min_len = @min(res.len, salt.len); - for (res[0..min_len], salt[0..min_len]) |*a, b| { - a.* = a.* ^ b; - } -} - -/// PBKDF2-HMAC-SHA512 implementation using bitcoin_hashes. -pub fn pbkdf2(mnemonic: []const []const u8, unprefixed_salt: []const u8, c: usize, res: []u8) void { - const prf = createHmacEngine(mnemonic); - @memset(res, 0); - - // var pprf = prf; - - // var prf_buf: [Hmac.mac_length]u8 = undefined; - // pprf.final(&prf_buf); - - // std.log.warn("pprf :{any}", .{prf_buf}); - - var i: usize = 0; - - while (i < res.len) : ({ - i += Sha512.digest_length; - }) { - const chunk_too = @min(res.len, i + Sha512.digest_length); - const chunk: []u8 = res[i..chunk_too]; - var salt = v: { - var prfc = prf; - prfc.update(SALT_PREFIX); - prfc.update(unprefixed_salt); - - var buf: [4]u8 = undefined; - std.mem.writeInt(u32, &buf, @truncate(i + 1), .big); - - prfc.update(&buf); - - var salt: [Hmac.mac_length]u8 = undefined; - - prfc.final(&salt); - - xor(chunk, &salt); - break :v salt; - }; - - for (1..c) |_| { - var prfc = prf; - - prfc.update(&salt); - - prfc.final(&salt); - xor(chunk, &salt); - } - } -} diff --git a/src/core/lib.zig b/src/core/lib.zig index 536ac92..5b56d5f 100644 --- a/src/core/lib.zig +++ b/src/core/lib.zig @@ -1,6 +1,4 @@ -pub const bip32 = @import("bip32/bip32.zig"); pub const dhke = @import("dhke.zig"); pub const secret = @import("secret.zig"); pub const amount = @import("amount.zig"); pub const nuts = @import("nuts/lib.zig"); -pub const bip39 = @import("bip39/bip39.zig"); diff --git a/src/core/nuts/nut02/nut02.zig b/src/core/nuts/nut02/nut02.zig index 99afaff..c93bdc7 100644 --- a/src/core/nuts/nut02/nut02.zig +++ b/src/core/nuts/nut02/nut02.zig @@ -8,7 +8,7 @@ const Keys = @import("../nut01/nut01.zig").Keys; const MintKeys = @import("../nut01/nut01.zig").MintKeys; const MintKeyPair = @import("../nut01/nut01.zig").MintKeyPair; const secp256k1 = @import("secp256k1"); -const bip32 = @import("../../bip32/bip32.zig"); +const bip32 = @import("bitcoin").bitcoin.bip32; /// Keyset version pub const KeySetVersion = enum { diff --git a/src/core/nuts/nut13/nut13.zig b/src/core/nuts/nut13/nut13.zig index ccdaa47..1ae7b87 100644 --- a/src/core/nuts/nut13/nut13.zig +++ b/src/core/nuts/nut13/nut13.zig @@ -10,12 +10,12 @@ const nut00 = @import("../nut00/lib.zig"); const BlindedMessage = nut00.BlindedMessage; const PreMint = nut00.PreMint; const PreMintSecrets = nut00.PreMintSecrets; -const bip32 = @import("../../bip32/bip32.zig"); +const bip32 = @import("bitcoin").bitcoin.bip32; const std = @import("std"); const amount_lib = @import("../../amount.zig"); const dhke = @import("../../dhke.zig"); const helper = @import("../../../helper/helper.zig"); -const bip39 = @import("../../bip39/bip39.zig"); +const bip39 = @import("bitcoin").bitcoin.bip39; fn derivePathFromKeysetId(id: Id) ![3]bip32.ChildNumber { const index: u32 = @intCast(try id.toU64() % ((std.math.powi(u64, 2, 31) catch unreachable) - 1)); diff --git a/src/lib.zig b/src/lib.zig index 467eba8..7ce22c1 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,12 +1,7 @@ const std = @import("std"); pub const core = @import("core/lib.zig"); -// pub usingnamespace @import("core/lib.zig"); -// TODO return after fix mint -// pub usingnamespace @import("mint/lib.zig"); -// pub const bech32 = @import("bech32/bech32.zig"); test { std.testing.log_level = .warn; std.testing.refAllDeclsRecursive(@This()); - // std.testing.refAllDeclsRecursive(bech32); } diff --git a/src/mint/lightning/invoices/invoice.zig b/src/mint/lightning/invoices/invoice.zig index 236c531..9d90ba4 100644 --- a/src/mint/lightning/invoices/invoice.zig +++ b/src/mint/lightning/invoices/invoice.zig @@ -2,7 +2,7 @@ const std = @import("std"); const errors = @import("error.zig"); const core = @import("../../../core/lib.zig"); const constants = @import("constants.zig"); -const bech32 = @import("../../../bech32/bech32.zig"); +const bech32 = @import("bitcoin").bech32; const secp256k1 = @import("secp256k1"); /// Construct the invoice's HRP and signatureless data into a preimage to be hashed.