From 4b0c47a12af51c9d05e089bf967889171a3c5df6 Mon Sep 17 00:00:00 2001 From: samooth Date: Sun, 23 Aug 2026 16:32:56 +0200 Subject: [PATCH 01/11] Migrate to Zig 0.16.0: replace std.crypto.random with util shim --- src/broadcast/http_post.zig | 2 +- src/crypto/compact.zig | 3 ++- src/crypto/ecies.zig | 3 ++- src/message/encrypted.zig | 3 ++- src/message/signed.zig | 3 ++- src/primitives/bip39.zig | 3 ++- src/primitives/ec.zig | 9 ++++---- src/primitives/symmetric.zig | 5 ++-- src/util.zig | 44 ++++++++++++++++++++++++++++++++++++ 9 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 src/util.zig diff --git a/src/broadcast/http_post.zig b/src/broadcast/http_post.zig index d78b84f..c4c8604 100644 --- a/src/broadcast/http_post.zig +++ b/src/broadcast/http_post.zig @@ -7,7 +7,7 @@ pub const PostResult = struct { fn readResponseBody(allocator: std.mem.Allocator, resp: *std.http.Client.Response, buf: []u8) ![]u8 { var body_reader = resp.reader(buf); - return body_reader.allocRemaining(allocator, std.io.Limit.limited(4 * 1024 * 1024)); + return body_reader.allocRemaining(allocator, std.Io.Limit.limited(4 * 1024 * 1024)); } /// POST with body; returns allocated response body. diff --git a/src/crypto/compact.zig b/src/crypto/compact.zig index df08739..7069d08 100644 --- a/src/crypto/compact.zig +++ b/src/crypto/compact.zig @@ -1,6 +1,7 @@ //! Bitcoin compact ECDSA signatures (65 bytes: recovery header + R + S), matching //! go-sdk `primitives/ec/signature.go` `SignCompact` / `RecoverCompact`. const std = @import("std"); +const util = @import("../util.zig"); const hash_mod = @import("hash.zig"); const secp256k1 = @import("secp256k1.zig"); @@ -174,7 +175,7 @@ test "compact sign and recover roundtrip (random keys)" { var rng_buf: [32]u8 = undefined; var c: u8 = 0; while (c < 16) : (c += 1) { - std.crypto.random.bytes(&rng_buf); + util.randomBytes(&rng_buf); const digest = hash_mod.hash256(&rng_buf).bytes; const sk = secp256k1.PrivateKey.fromBytes(rng_buf) catch continue; const compressed = (c & 1) == 1; diff --git a/src/crypto/ecies.zig b/src/crypto/ecies.zig index 46106fc..458492c 100644 --- a/src/crypto/ecies.zig +++ b/src/crypto/ecies.zig @@ -1,5 +1,6 @@ //! Electrum (BIE1) and Bitcore-style ECIES on secp256k1. Matches go-sdk `compat/ecies`. const std = @import("std"); +const util = @import("../util.zig"); const aescbc = @import("../primitives/aescbc.zig"); const hash = @import("hash.zig"); const secp = @import("secp256k1.zig"); @@ -23,7 +24,7 @@ fn hmacEqual32(a: []const u8, b: *const [32]u8) bool { fn randomPrivateKey() Error!secp.PrivateKey { var buf: [32]u8 = undefined; for (0..256) |_| { - std.crypto.random.bytes(&buf); + util.randomBytes(&buf); if (secp.PrivateKey.fromBytes(buf)) |k| return k else |_| {} } return error.KeyGenFailed; diff --git a/src/message/encrypted.zig b/src/message/encrypted.zig index 98aca40..b106f70 100644 --- a/src/message/encrypted.zig +++ b/src/message/encrypted.zig @@ -1,5 +1,6 @@ //! BRC-78 portable encrypted messages (go-sdk `message/encrypted.go`). const std = @import("std"); +const util = @import("../util.zig"); const ec = @import("../primitives/ec.zig"); const symmetric = @import("../primitives/symmetric.zig"); @@ -59,7 +60,7 @@ pub fn encryptAlloc( recipient: ec.PublicKey, ) ![]u8 { var key_id: [32]u8 = undefined; - std.crypto.random.bytes(&key_id); + util.randomBytes(&key_id); return encryptAllocWithKeyId(allocator, message, sender, recipient, key_id); } diff --git a/src/message/signed.zig b/src/message/signed.zig index c50087c..65fcfdd 100644 --- a/src/message/signed.zig +++ b/src/message/signed.zig @@ -1,5 +1,6 @@ //! BRC-77 portable signed messages (go-sdk `message/signed.go` wire: `BB3\\x01`, invoice `2-message signing-…`). const std = @import("std"); +const util = @import("../util.zig"); const ec = @import("../primitives/ec.zig"); const DerSignature = @import("../crypto/signature.zig").DerSignature; @@ -73,7 +74,7 @@ pub fn signAlloc( recipient: ?ec.PublicKey, ) ![]u8 { var key_id: [32]u8 = undefined; - std.crypto.random.bytes(&key_id); + util.randomBytes(&key_id); return signAllocWithKeyId(allocator, message, signer, recipient, key_id); } diff --git a/src/primitives/bip39.zig b/src/primitives/bip39.zig index 0e80da5..4d661ab 100644 --- a/src/primitives/bip39.zig +++ b/src/primitives/bip39.zig @@ -2,6 +2,7 @@ //! Behavior matches github.com/bsv-blockchain/go-sdk compat/bip39 (big.Int checksum path). const std = @import("std"); +const util = @import("../util.zig"); const hex = @import("hex.zig"); const StaticStringMap = std.static_string_map.StaticStringMap; const HmacSha512 = std.crypto.auth.hmac.sha2.HmacSha512; @@ -52,7 +53,7 @@ pub fn newEntropy(allocator: std.mem.Allocator, bit_size: usize) Error![]u8 { try validateEntropyBits(bit_size); const n = bit_size / 8; const buf = try allocator.alloc(u8, n); - std.crypto.random.bytes(buf); + util.randomBytes(buf); return buf; } diff --git a/src/primitives/ec.zig b/src/primitives/ec.zig index 9ddf76d..2abf213 100644 --- a/src/primitives/ec.zig +++ b/src/primitives/ec.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const util = @import("../util.zig"); const secp256k1 = @import("../crypto/secp256k1.zig"); const sig = @import("../crypto/signature.zig"); const compact = @import("../crypto/compact.zig"); @@ -57,7 +58,7 @@ pub const PrivateKey = struct { pub fn generate() !PrivateKey { var bytes: [32]u8 = undefined; while (true) { - std.crypto.random.bytes(&bytes); + util.randomBytes(&bytes); if (secp256k1.PrivateKey.fromBytes(bytes)) |key| { return .{ .inner = key }; } else |_| { @@ -163,7 +164,7 @@ pub const PrivateKey = struct { errdefer allocator.free(points); var seed: [64]u8 = undefined; - std.crypto.random.bytes(&seed); + util.randomBytes(&seed); var used = std.AutoHashMap(u256, void).init(allocator); defer used.deinit(); @@ -176,7 +177,7 @@ pub const PrivateKey = struct { var counter: [40]u8 = undefined; std.mem.writeInt(u32, counter[0..4], @intCast(i), .big); std.mem.writeInt(u32, counter[4..8], attempt, .big); - std.crypto.random.bytes(counter[8..40]); + util.randomBytes(counter[8..40]); const h = crypto_hash.hmacSha512(counter[0..], &seed); x = bytesToU256(h[0..32]) % keyshares.Curve.p; if (x == 0) continue; @@ -394,7 +395,7 @@ fn bytesToU256(bytes: []const u8) u256 { fn randomFieldNonZero() u256 { var out: [32]u8 = undefined; while (true) { - std.crypto.random.bytes(&out); + util.randomBytes(&out); const v = std.mem.readInt(u256, &out, .big) % keyshares.Curve.p; if (v != 0) return v; } diff --git a/src/primitives/symmetric.zig b/src/primitives/symmetric.zig index b02dd25..1aae1ed 100644 --- a/src/primitives/symmetric.zig +++ b/src/primitives/symmetric.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const util = @import("../util.zig"); const aesgcm = @import("aesgcm.zig"); pub const Error = error{ @@ -20,7 +21,7 @@ pub const SymmetricKey = struct { pub fn newFromRandom() SymmetricKey { var out = [_]u8{0} ** 32; - std.crypto.random.bytes(&out); + util.randomBytes(&out); return .{ .key = out }; } @@ -43,7 +44,7 @@ pub const SymmetricKey = struct { plaintext: []const u8, ) ![]u8 { var iv: [32]u8 = undefined; - std.crypto.random.bytes(&iv); + util.randomBytes(&iv); const enc = try aesgcm.aesGcmEncrypt(allocator, plaintext, &self.key, &iv, ""); defer allocator.free(enc.ciphertext); diff --git a/src/util.zig b/src/util.zig new file mode 100644 index 0000000..508e3b4 --- /dev/null +++ b/src/util.zig @@ -0,0 +1,44 @@ +//! Platform shim for std facilities removed in Zig 0.16. + +const std = @import("std"); +const builtin = @import("builtin"); + +/// Fill `buf` with cryptographically secure random bytes. +pub fn randomBytes(buf: []u8) void { + if (builtin.os.tag == .linux) { + var filled: usize = 0; + while (filled < buf.len) { + filled += std.os.linux.getrandom(buf[filled..].ptr, buf.len - filled, 0); + } + } else { + arc4random_buf(buf.ptr, buf.len); + } +} + +extern "c" fn arc4random_buf(buf: [*]u8, nbytes: usize) void; + +/// Milliseconds since the Unix epoch. +pub fn nowMilli() i64 { + var ts: std.c.timespec = undefined; + if (builtin.os.tag == .linux) { + _ = std.os.linux.clock_gettime(.REALTIME, &ts); + } else { + _ = std.c.clock_gettime(.REALTIME, &ts); + } + return @as(i64, ts.sec) * 1000 + @divTrunc(@as(i64, ts.nsec), 1_000_000); +} + +/// Seconds since the Unix epoch. +pub fn nowSecs() u64 { + var ts: std.c.timespec = undefined; + if (builtin.os.tag == .linux) { + _ = std.os.linux.clock_gettime(.REALTIME, &ts); + } else { + _ = std.c.clock_gettime(.REALTIME, &ts); + } + return @intCast(ts.sec); +} + +test "nowSecs is plausible" { + try std.testing.expect(nowSecs() > 1_700_000_000); +} From 4db0106e683636428a191cd36c0a26e5fbc1b343 Mon Sep 17 00:00:00 2001 From: samooth Date: Sun, 23 Aug 2026 17:01:46 +0200 Subject: [PATCH 02/11] Complete Zig 0.16 migration: Io-threaded broadcast API, updated docs/examples/tests/bench --- README.md | 13 ++++++- benchmarks/script_engine.zig | 49 +++++++++++++++---------- docs/examples/script-verification.md | 11 +++++- examples/gorillapool_arc_demo.zig | 5 ++- examples/prevout_trace_demo.zig | 12 +++++- examples/script_trace_demo.zig | 12 +++++- src/broadcast/arc.zig | 12 ++++-- src/broadcast/http_post.zig | 6 ++- src/broadcast/taal.zig | 2 + src/broadcast/woc.zig | 3 +- src/lib.zig | 2 +- src/script/context.zig | 12 +++--- src/script/engine.zig | 9 ++--- src/script/thread.zig | 22 +++++------ src/transaction/beef.zig | 2 +- src/util.zig | 15 ++++++++ tests/external_coverage_notice.zig | 35 +++++++++++++++--- tests/go_corpus_accounting.zig | 22 ++++++++--- tests/go_corpus_filtered_vectors.zig | 13 ++++++- tests/go_exact_corpus_vectors.zig | 13 ++++++- tests/go_meta_rows_vectors.zig | 13 ++++++- tests/go_multisig_reference_vectors.zig | 15 ++++++-- tests/go_sigcheck_reference_vectors.zig | 15 ++++++-- 23 files changed, 229 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 643d981..dbda64c 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Crypto, keys, script, transactions, SPV, BEEF, and broadcast. 27 BRC standards c ## Getting Started -**Requirements:** Zig `0.15.2` +**Requirements:** Zig `0.16.0` Fetch the dependency: @@ -205,7 +205,16 @@ var traced = bsvz.script.thread.verifyScriptsTraced(.{ })); defer traced.deinit(allocator); -try traced.writeDebug(std.io.getStdOut().writer()); +// Zig 0.16: stdout writing requires an Io instance and a buffer. +var threaded = std.Io.Threaded.init(allocator, .{ .environ = .empty }); +defer threaded.deinit(); + +var stdout_buffer: [4096]u8 = undefined; +var stdout_writer = std.Io.File.stdout().writer(threaded.io(), &stdout_buffer); +const stdout = &stdout_writer.interface; + +try traced.writeDebug(stdout); +try stdout.flush(); ``` ### Output serialization diff --git a/benchmarks/script_engine.zig b/benchmarks/script_engine.zig index 2459375..b23f42f 100644 --- a/benchmarks/script_engine.zig +++ b/benchmarks/script_engine.zig @@ -8,7 +8,7 @@ const engine = bsvz.script.engine; const iterations = 10_000; const fixture_allocator = std.heap.page_allocator; -fn bench(comptime name: []const u8, comptime run_fn: fn (std.mem.Allocator) void) void { +fn bench(io: std.Io, comptime name: []const u8, comptime run_fn: fn (std.mem.Allocator) void) void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -17,14 +17,13 @@ fn bench(comptime name: []const u8, comptime run_fn: fn (std.mem.Allocator) void run_fn(arena.allocator()); } - const start = std.time.nanoTimestamp(); + const start = std.Io.Timestamp.now(io, .awake); for (0..iterations) |_| { _ = arena.reset(.retain_capacity); run_fn(arena.allocator()); } - const end = std.time.nanoTimestamp(); - - const elapsed_ns: u64 = @intCast(end - start); + const end = std.Io.Timestamp.now(io, .awake); + const elapsed_ns: u64 = @intCast(end.nanoseconds - start.nanoseconds); const per_iter_ns = elapsed_ns / iterations; const per_iter_us = per_iter_ns / 1_000; const ops_per_sec = if (per_iter_ns > 0) 1_000_000_000 / per_iter_ns else 0; @@ -140,9 +139,9 @@ fn pairRunarArithmetic() ScriptPair { return .{ .unlocking = &runar_arithmetic_unlocking, .locking = &runar_arithmetic_locking }; } -var p2pkh_fixture_once = std.once(initP2PKHFixture); +var p2pkh_fixture_initialized = false; var p2pkh_fixture: PrevoutFixture = undefined; -var go_reference_p2pkh_fixture_once = std.once(initGoReferenceP2PKHFixture); +var go_reference_p2pkh_fixture_initialized = false; var go_reference_p2pkh_fixture: ReferencePrevoutFixture = undefined; fn initP2PKHFixture() void { @@ -211,7 +210,10 @@ fn initP2PKHFixture() void { } fn getP2PKHFixture() *const PrevoutFixture { - p2pkh_fixture_once.call(); + if (!p2pkh_fixture_initialized) { + initP2PKHFixture(); + p2pkh_fixture_initialized = true; + } return &p2pkh_fixture; } @@ -232,7 +234,10 @@ fn initGoReferenceP2PKHFixture() void { } fn getGoReferenceP2PKHFixture() *const ReferencePrevoutFixture { - go_reference_p2pkh_fixture_once.call(); + if (!go_reference_p2pkh_fixture_initialized) { + initGoReferenceP2PKHFixture(); + go_reference_p2pkh_fixture_initialized = true; + } return &go_reference_p2pkh_fixture; } @@ -333,20 +338,24 @@ pub fn main() !void { _ = getP2PKHFixture(); _ = getGoReferenceP2PKHFixture(); + var threaded = std.Io.Threaded.init(std.heap.page_allocator, .{ .environ = .empty }); + defer threaded.deinit(); + const io = threaded.io(); + std.debug.print("\nbsvz script engine benchmarks ({d} iterations each)\n", .{iterations}); std.debug.print("{s}\n", .{"=" ** 90}); - bench("arithmetic verify (2+3==5)", benchArithmetic); - bench("branching verify (if/else)", benchBranching); - bench("OP_SHA256 verify (32-byte input)", benchSha256); - bench("OP_HASH160 verify (20-byte input)", benchHash160); - bench("stack ops verify", benchStackOps); - bench("runar arithmetic verify", benchRunarArithmetic); - bench("P2PKH sighash only", benchP2PKHSighash); - bench("P2PKH secp verify only", benchP2PKHSecpVerify); - bench("P2PKH secp verify only (parsed)", benchP2PKHSecpVerifyParsed); - bench("P2PKH verify (synthetic fixture)", benchP2PKHVerify); - bench("P2PKH verify (Go reference tx)", benchGoReferenceP2PKHVerify); + bench(io, "arithmetic verify (2+3==5)", benchArithmetic); + bench(io, "branching verify (if/else)", benchBranching); + bench(io, "OP_SHA256 verify (32-byte input)", benchSha256); + bench(io, "OP_HASH160 verify (20-byte input)", benchHash160); + bench(io, "stack ops verify", benchStackOps); + bench(io, "runar arithmetic verify", benchRunarArithmetic); + bench(io, "P2PKH sighash only", benchP2PKHSighash); + bench(io, "P2PKH secp verify only", benchP2PKHSecpVerify); + bench(io, "P2PKH secp verify only (parsed)", benchP2PKHSecpVerifyParsed); + bench(io, "P2PKH verify (synthetic fixture)", benchP2PKHVerify); + bench(io, "P2PKH verify (Go reference tx)", benchGoReferenceP2PKHVerify); std.debug.print("{s}\n", .{"=" ** 90}); } diff --git a/docs/examples/script-verification.md b/docs/examples/script-verification.md index 2f3a022..4a726b3 100644 --- a/docs/examples/script-verification.md +++ b/docs/examples/script-verification.md @@ -51,7 +51,16 @@ var traced = bsvz.script.thread.verifyScriptsTraced( ); defer traced.deinit(allocator); -try traced.writeDebug(std.io.getStdOut().writer()); +// Zig 0.16: stdout writing requires an Io instance and a buffer. +var threaded = std.Io.Threaded.init(allocator, .{ .environ = .empty }); +defer threaded.deinit(); + +var stdout_buffer: [4096]u8 = undefined; +var stdout_writer = std.Io.File.stdout().writer(threaded.io(), &stdout_buffer); +const stdout = &stdout_writer.interface; + +try traced.writeDebug(stdout); +try stdout.flush(); ``` Runnable examples: diff --git a/examples/gorillapool_arc_demo.zig b/examples/gorillapool_arc_demo.zig index 9a40dec..bbf4ccf 100644 --- a/examples/gorillapool_arc_demo.zig +++ b/examples/gorillapool_arc_demo.zig @@ -18,7 +18,10 @@ pub fn main() !void { .api_url = "https://arc.gorillapool.io", }; - var result = try broadcaster.broadcast(allocator, &tx); + var threaded = std.Io.Threaded.init(allocator, .{ .environ = .empty }); + defer threaded.deinit(); + + var result = try broadcaster.broadcast(allocator, threaded.io(), &tx); defer result.deinit(allocator); switch (result) { diff --git a/examples/prevout_trace_demo.zig b/examples/prevout_trace_demo.zig index 47c28c8..3c14bb8 100644 --- a/examples/prevout_trace_demo.zig +++ b/examples/prevout_trace_demo.zig @@ -36,6 +36,14 @@ pub fn main() !void { }); defer traced.deinit(allocator); - try traced.writeDebug(std.io.getStdOut().writer()); - try std.io.getStdOut().writer().writeByte('\n'); + var threaded = std.Io.Threaded.init(allocator, .{ .environ = .empty }); + defer threaded.deinit(); + + var stdout_buffer: [4096]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(threaded.io(), &stdout_buffer); + const stdout = &stdout_writer.interface; + + try traced.writeDebug(stdout); + try stdout.writeByte('\n'); + try stdout.flush(); } diff --git a/examples/script_trace_demo.zig b/examples/script_trace_demo.zig index a360738..74d93d7 100644 --- a/examples/script_trace_demo.zig +++ b/examples/script_trace_demo.zig @@ -12,6 +12,14 @@ pub fn main() !void { })); defer traced.deinit(allocator); - try traced.writeDebug(std.io.getStdOut().writer()); - try std.io.getStdOut().writer().writeByte('\n'); + var threaded = std.Io.Threaded.init(allocator, .{ .environ = .empty }); + defer threaded.deinit(); + + var stdout_buffer: [4096]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(threaded.io(), &stdout_buffer); + const stdout = &stdout_writer.interface; + + try traced.writeDebug(stdout); + try stdout.writeByte('\n'); + try stdout.flush(); } diff --git a/src/broadcast/arc.zig b/src/broadcast/arc.zig index 8d827cc..19f2a2e 100644 --- a/src/broadcast/arc.zig +++ b/src/broadcast/arc.zig @@ -140,6 +140,7 @@ pub const Arc = struct { fn arcPost( self: *const Arc, allocator: std.mem.Allocator, + io: std.Io, tx: *const transaction.Transaction, ) !http_post.PostResult { const payload = try self.arcPayload(allocator, tx); @@ -195,16 +196,17 @@ pub const Arc = struct { try hdrs.append(allocator, .{ .name = "X-WaitFor", .value = self.wait_for }); } - return try http_post.postBodyAlloc(allocator, url, hdrs.items, payload); + return try http_post.postBodyAlloc(allocator, io, url, hdrs.items, payload); } /// POST /tx — returns parsed JSON tree (caller `deinit`s). pub fn arcBroadcast( self: *const Arc, allocator: std.mem.Allocator, + io: std.Io, tx: *const transaction.Transaction, ) !std.json.Parsed(std.json.Value) { - const post = try self.arcPost(allocator, tx); + const post = try self.arcPost(allocator, io, tx); defer allocator.free(post.body); if (self.verbose) { @@ -217,9 +219,10 @@ pub const Arc = struct { pub fn broadcast( self: *const Arc, allocator: std.mem.Allocator, + io: std.Io, tx: *const transaction.Transaction, ) !types.BroadcastResult { - const post = self.arcPost(allocator, tx) catch |err| { + const post = self.arcPost(allocator, io, tx) catch |err| { return broadcastError(allocator, .internal_server_error, @errorName(err)); }; defer allocator.free(post.body); @@ -235,6 +238,7 @@ pub const Arc = struct { pub fn status( self: *const Arc, allocator: std.mem.Allocator, + io: std.Io, txid_hex: []const u8, ) !std.json.Parsed(std.json.Value) { const url = try joinUrl(allocator, self.api_url, "tx"); @@ -251,7 +255,7 @@ pub const Arc = struct { try hdrs.append(allocator, .{ .name = "Authorization", .value = auth_bearer.? }); } - const get = try http_post.getBodyAlloc(allocator, full, hdrs.items); + const get = try http_post.getBodyAlloc(allocator, io, full, hdrs.items); defer allocator.free(get.body); return std.json.parseFromSlice(std.json.Value, allocator, get.body, .{}); diff --git a/src/broadcast/http_post.zig b/src/broadcast/http_post.zig index c4c8604..4781b32 100644 --- a/src/broadcast/http_post.zig +++ b/src/broadcast/http_post.zig @@ -13,11 +13,12 @@ fn readResponseBody(allocator: std.mem.Allocator, resp: *std.http.Client.Respons /// POST with body; returns allocated response body. pub fn postBodyAlloc( allocator: std.mem.Allocator, + io: std.Io, url: []const u8, extra_headers: []const std.http.Header, payload: []const u8, ) !PostResult { - var client: std.http.Client = .{ .allocator = allocator }; + var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); const uri = try std.Uri.parse(url); @@ -43,10 +44,11 @@ pub fn postBodyAlloc( /// GET; returns allocated response body. pub fn getBodyAlloc( allocator: std.mem.Allocator, + io: std.Io, url: []const u8, extra_headers: []const std.http.Header, ) !PostResult { - var client: std.http.Client = .{ .allocator = allocator }; + var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); const uri = try std.Uri.parse(url); diff --git a/src/broadcast/taal.zig b/src/broadcast/taal.zig index 1f38a97..4a092a6 100644 --- a/src/broadcast/taal.zig +++ b/src/broadcast/taal.zig @@ -10,6 +10,7 @@ pub const TAALBroadcast = struct { pub fn broadcast( self: TAALBroadcast, allocator: std.mem.Allocator, + io: std.Io, tx: *const transaction.Transaction, ) !types.BroadcastResult { const serialized = try tx.serialize(allocator); @@ -24,6 +25,7 @@ pub const TAALBroadcast = struct { const post = try http_post.postBodyAlloc( allocator, + io, "https://api.taal.com/api/v1/broadcast", hdrs.items, serialized, diff --git a/src/broadcast/woc.zig b/src/broadcast/woc.zig index 2dee05b..24c80dc 100644 --- a/src/broadcast/woc.zig +++ b/src/broadcast/woc.zig @@ -23,6 +23,7 @@ pub const WhatsOnChain = struct { pub fn broadcast( self: WhatsOnChain, allocator: std.mem.Allocator, + io: std.Io, tx: *const transaction.Transaction, ) !types.BroadcastResult { const serialized = try tx.serialize(allocator); @@ -51,7 +52,7 @@ pub const WhatsOnChain = struct { try hdrs.append(allocator, .{ .name = "Authorization", .value = auth_bearer.? }); } - const post = try http_post.postBodyAlloc(allocator, url, hdrs.items, body); + const post = try http_post.postBodyAlloc(allocator, io, url, hdrs.items, body); defer allocator.free(post.body); if (post.status != .ok) { diff --git a/src/lib.zig b/src/lib.zig index 22d9121..79abaae 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -12,5 +12,5 @@ pub const compat = @import("compat/lib.zig"); pub const message = @import("message/lib.zig"); test { - @import("std").testing.refAllDeclsRecursive(@This()); + @import("util.zig").refAllDeclsRecursive(@This()); } diff --git a/src/script/context.zig b/src/script/context.zig index d27cd55..f2a59c1 100644 --- a/src/script/context.zig +++ b/src/script/context.zig @@ -336,10 +336,10 @@ test "execution trace captures independent snapshots" { try std.testing.expectEqual(opcode.Opcode.OP_DUP, trace.steps.items[0].opcodeValue()); try std.testing.expectEqualStrings("OP_DUP", trace.steps.items[0].opcodeName()); - var rendered: std.ArrayListUnmanaged(u8) = .empty; - defer rendered.deinit(allocator); - try trace.writeDebug(rendered.writer(allocator)); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "ExecutionTrace(steps=1)") != null); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "OP_DUP") != null); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "0x76") != null); + var rendered: std.Io.Writer.Allocating = .init(allocator); + defer rendered.deinit(); + try trace.writeDebug(&rendered.writer); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "ExecutionTrace(steps=1)") != null); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "OP_DUP") != null); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "0x76") != null); } diff --git a/src/script/engine.zig b/src/script/engine.zig index f85cf28..6dc09b8 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -1164,12 +1164,9 @@ fn verifyChecksigWithScriptCode( } try checkHashTypeEncoding(ctx, hash_type); - const tx_signature = crypto.TxSignature.fromChecksigFormat(sig_bytes) catch |err| switch (err) { - error.InvalidEncoding => { - if (check_signature_encoding) return error.InvalidSignatureEncoding; - return false; - }, - else => return err, + const tx_signature = crypto.TxSignature.fromChecksigFormat(sig_bytes) catch { + if (check_signature_encoding) return error.InvalidSignatureEncoding; + return false; }; _ = (if (check_pubkey_encoding) crypto.PublicKey.fromSec1(pubkey_bytes) diff --git a/src/script/thread.zig b/src/script/thread.zig index 2bb757c..98ca6bd 100644 --- a/src/script/thread.zig +++ b/src/script/thread.zig @@ -876,12 +876,12 @@ test "thread verifyScriptsTraced captures opcode snapshots before terminal failu try std.testing.expectEqualStrings("OP_FROMALTSTACK", traced.failureStep().?.opcodeName()); try std.testing.expectEqualDeep(VerificationOutcome{ .script_error = error.AltStackUnderflow }, traced.outcome()); - var rendered: std.ArrayListUnmanaged(u8) = .empty; - defer rendered.deinit(allocator); - try traced.writeDebug(rendered.writer(allocator)); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "VerificationResult") != null); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "AltStackUnderflow") != null); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "OP_FROMALTSTACK") != null); + var rendered: std.Io.Writer.Allocating = .init(allocator); + defer rendered.deinit(); + try traced.writeDebug(&rendered.writer); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "VerificationResult") != null); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "AltStackUnderflow") != null); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "OP_FROMALTSTACK") != null); } test "thread verificationOutcome maps legacy bool-or-error results" { @@ -892,12 +892,12 @@ test "thread verificationOutcome maps legacy bool-or-error results" { verificationOutcome(@as(Error!bool, error.CleanStack)), ); - var rendered: std.ArrayListUnmanaged(u8) = .empty; - defer rendered.deinit(std.testing.allocator); + var rendered: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer rendered.deinit(); const outcome = VerificationOutcome{ .script_error = error.CleanStack }; - try outcome.writeDebug(rendered.writer(std.testing.allocator)); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "script_error(") != null); - try std.testing.expect(std.mem.indexOf(u8, rendered.items, "CleanStack") != null); + try outcome.writeDebug(&rendered.writer); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "script_error(") != null); + try std.testing.expect(std.mem.indexOf(u8, rendered.written(), "CleanStack") != null); } test "thread verifyPrevoutSpendDetailed uses previous output directly" { diff --git a/src/transaction/beef.zig b/src/transaction/beef.zig index d8af443..eb729b7 100644 --- a/src/transaction/beef.zig +++ b/src/transaction/beef.zig @@ -752,7 +752,7 @@ fn readTransactionsV2(beef: *Beef, bytes: []const u8, cursor: *usize) !void { for (0..count) |_| { if (bytes.len < cursor.* + 1) return error.EndOfStream; const raw_format = bytes[cursor.*]; - const format: DataFormat = std.meta.intToEnum(DataFormat, raw_format) catch return error.InvalidEncoding; + const format: DataFormat = std.enums.fromInt(DataFormat, raw_format) orelse return error.InvalidEncoding; cursor.* += 1; switch (format) { diff --git a/src/util.zig b/src/util.zig index 508e3b4..fee3dff 100644 --- a/src/util.zig +++ b/src/util.zig @@ -42,3 +42,18 @@ pub fn nowSecs() u64 { test "nowSecs is plausible" { try std.testing.expect(nowSecs() > 1_700_000_000); } + +/// Replacement for `std.testing.refAllDeclsRecursive`, removed in Zig 0.16. +pub fn refAllDeclsRecursive(comptime T: type) void { + if (!builtin.is_test) return; + inline for (comptime std.meta.declarations(T)) |decl| { + const D = @field(T, decl.name); + if (comptime @TypeOf(D) == type) { + switch (@typeInfo(D)) { + .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(D), + else => {}, + } + } + _ = &D; + } +} diff --git a/tests/external_coverage_notice.zig b/tests/external_coverage_notice.zig index f6ca252..e8c8a68 100644 --- a/tests/external_coverage_notice.zig +++ b/tests/external_coverage_notice.zig @@ -1,5 +1,14 @@ const std = @import("std"); +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} + const ExternalInput = struct { name: []const u8, path: []const u8, @@ -16,11 +25,25 @@ const external_inputs = [_]ExternalInput{ }; fn envRequiresExternalCoverage(allocator: std.mem.Allocator) bool { - const value = std.process.getEnvVarOwned(allocator, "BSVZ_REQUIRE_EXTERNAL_CORPORA") catch return false; - defer allocator.free(value); - return std.mem.eql(u8, value, "1") or - std.ascii.eqlIgnoreCase(value, "true") or - std.ascii.eqlIgnoreCase(value, "yes"); + // Zig 0.16 removed std.process.getEnvVarOwned; without libc the process + // environment is only reachable via /proc/self/environ on Linux. + if (@import("builtin").os.tag != .linux) return false; + const io = testIo(); + var proc_dir = std.Io.Dir.openDirAbsolute(io, "/proc/self", .{}) catch return false; + defer proc_dir.close(io); + const data = proc_dir.readFileAlloc(io, "environ", allocator, .limited(1024 * 1024)) catch return false; + defer allocator.free(data); + + var it = std.mem.splitScalar(u8, data, 0); + while (it.next()) |entry| { + const eq = std.mem.indexOfScalar(u8, entry, '=') orelse continue; + if (!std.mem.eql(u8, entry[0..eq], "BSVZ_REQUIRE_EXTERNAL_CORPORA")) continue; + const value = entry[eq + 1 ..]; + return std.mem.eql(u8, value, "1") or + std.ascii.eqlIgnoreCase(value, "true") or + std.ascii.eqlIgnoreCase(value, "yes"); + } + return false; } test "external corpus availability is visible in default test runs" { @@ -34,7 +57,7 @@ test "external corpus availability is visible in default test runs" { ); for (external_inputs) |input| { - std.fs.cwd().access(input.path, .{}) catch |err| switch (err) { + std.Io.Dir.cwd().access(testIo(), input.path, .{}) catch |err| switch (err) { error.FileNotFound => { missing_count += 1; if (input.optional_step) |step| { diff --git a/tests/go_corpus_accounting.zig b/tests/go_corpus_accounting.zig index 87a7cfa..2f64a74 100644 --- a/tests/go_corpus_accounting.zig +++ b/tests/go_corpus_accounting.zig @@ -1,9 +1,18 @@ const std = @import("std"); +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} + const corpus_path = "../go-sdk/script/interpreter/data/script_tests.json"; fn accessOrRequire(rel_path: []const u8) !void { - try std.fs.cwd().access(rel_path, .{}); + try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); } const RowAccounting = struct { @@ -20,16 +29,17 @@ fn collectAccountedRowRefs(allocator: std.mem.Allocator) !RowAccounting { var counts = std.AutoHashMap(usize, usize).init(allocator); errdefer counts.deinit(); - var dir = try std.fs.cwd().openDir("tests", .{ .iterate = true }); - defer dir.close(); + const io = testIo(); + var dir = try std.Io.Dir.cwd().openDir(io, "tests", .{ .iterate = true }); + defer dir.close(io); var iter = dir.iterate(); - while (try iter.next()) |dir_entry| { + while (try iter.next(io)) |dir_entry| { if (dir_entry.kind != .file) continue; if (!std.mem.startsWith(u8, dir_entry.name, "go_")) continue; if (!std.mem.endsWith(u8, dir_entry.name, "_vectors.zig")) continue; - const source = try dir.readFileAlloc(allocator, dir_entry.name, 512 * 1024); + const source = try dir.readFileAlloc(io, dir_entry.name, allocator, .limited(512 * 1024)); defer allocator.free(source); var cursor: usize = 0; @@ -83,7 +93,7 @@ test "all go corpus rows are explicitly accounted for" { var accounting = try collectAccountedRowRefs(allocator); defer accounting.deinit(); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); diff --git a/tests/go_corpus_filtered_vectors.zig b/tests/go_corpus_filtered_vectors.zig index a3e0611..a9ce873 100644 --- a/tests/go_corpus_filtered_vectors.zig +++ b/tests/go_corpus_filtered_vectors.zig @@ -1,4 +1,13 @@ const std = @import("std"); + +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} const bsvz = @import("bsvz"); const reference_harness = @import("support/go_reference_harness.zig"); @@ -32,7 +41,7 @@ const SkipReason = enum { }; fn accessOrRequire(rel_path: []const u8) !void { - try std.fs.cwd().access(rel_path, .{}); + try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); } fn containsToken(script_asm: []const u8, needle: []const u8) bool { @@ -394,7 +403,7 @@ test "filtered go corpus rows execute through bsvz" { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); diff --git a/tests/go_exact_corpus_vectors.zig b/tests/go_exact_corpus_vectors.zig index e624e97..d6561ce 100644 --- a/tests/go_exact_corpus_vectors.zig +++ b/tests/go_exact_corpus_vectors.zig @@ -1,4 +1,13 @@ const std = @import("std"); + +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} const bsvz = @import("bsvz"); const harness = @import("support/go_reference_harness.zig"); @@ -18,7 +27,7 @@ const DynamicRow = struct { }; fn accessOrRequire(rel_path: []const u8) !void { - try std.fs.cwd().access(rel_path, .{}); + try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); } fn containsToken(script_asm: []const u8, needle: []const u8) bool { @@ -206,7 +215,7 @@ test "exact go corpus rows execute through bsvz" { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); diff --git a/tests/go_meta_rows_vectors.zig b/tests/go_meta_rows_vectors.zig index 6693423..489e500 100644 --- a/tests/go_meta_rows_vectors.zig +++ b/tests/go_meta_rows_vectors.zig @@ -1,5 +1,14 @@ const std = @import("std"); +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} + const corpus_path = "../go-sdk/script/interpreter/data/script_tests.json"; const MetaRow = struct { @@ -8,14 +17,14 @@ const MetaRow = struct { }; fn accessOrRequire(rel_path: []const u8) !void { - try std.fs.cwd().access(rel_path, .{}); + try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); } fn expectMetaRows(rows: []const MetaRow) !void { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); diff --git a/tests/go_multisig_reference_vectors.zig b/tests/go_multisig_reference_vectors.zig index 82355d0..af239db 100644 --- a/tests/go_multisig_reference_vectors.zig +++ b/tests/go_multisig_reference_vectors.zig @@ -1,4 +1,13 @@ const std = @import("std"); + +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} const bsvz = @import("bsvz"); const harness = @import("support/go_reference_harness.zig"); @@ -25,7 +34,7 @@ const SkipReason = enum { }; fn accessOrRequire(rel_path: []const u8) !void { - try std.fs.cwd().access(rel_path, .{}); + try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); } fn containsToken(script_asm: []const u8, needle: []const u8) bool { @@ -169,7 +178,7 @@ test "filtered go multisig reference rows execute through bsvz" { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); @@ -229,7 +238,7 @@ test "exact go multisig dynamic reference rows execute through bsvz" { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); diff --git a/tests/go_sigcheck_reference_vectors.zig b/tests/go_sigcheck_reference_vectors.zig index a7e3ad6..dc48740 100644 --- a/tests/go_sigcheck_reference_vectors.zig +++ b/tests/go_sigcheck_reference_vectors.zig @@ -1,4 +1,13 @@ const std = @import("std"); + +var test_threaded: ?std.Io.Threaded = null; + +fn testIo() std.Io { + if (test_threaded == null) { + test_threaded = std.Io.Threaded.init(std.testing.allocator, .{ .environ = .empty }); + } + return test_threaded.?.io(); +} const bsvz = @import("bsvz"); const harness = @import("support/go_reference_harness.zig"); @@ -26,7 +35,7 @@ const SkipReason = enum { }; fn accessOrRequire(rel_path: []const u8) !void { - try std.fs.cwd().access(rel_path, .{}); + try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); } fn containsToken(script_asm: []const u8, needle: []const u8) bool { @@ -163,7 +172,7 @@ test "filtered go sigcheck reference rows execute through bsvz" { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); @@ -223,7 +232,7 @@ test "exact go sigcheck dynamic reference rows execute through bsvz" { const allocator = std.testing.allocator; try accessOrRequire(corpus_path); - const file = try std.fs.cwd().readFileAlloc(allocator, corpus_path, 8 * 1024 * 1024); + const file = try std.Io.Dir.cwd().readFileAlloc(testIo(), corpus_path, allocator, .limited(8 * 1024 * 1024)); defer allocator.free(file); const parsed = try std.json.parseFromSlice(std.json.Value, allocator, file, .{}); From 6ce3253e12c58a9c978bf74aeea886ade3d3afe3 Mon Sep 17 00:00:00 2001 From: samooth Date: Sun, 23 Aug 2026 20:57:28 +0200 Subject: [PATCH 03/11] Support Zig 0.17 master: replace ** with @splat, u512 primitive, decl-string API, ArrayList empty init, graceful corpus skips, CI matrix --- .github/workflows/ci.yml | 23 +++ README.md | 2 +- benchmarks/script_engine.zig | 14 +- examples/prevout_trace_demo.zig | 2 +- src/broadcast/arc.zig | 2 +- src/compat/address.zig | 10 +- src/compat/bsm.zig | 4 +- src/compat/wif.zig | 8 +- src/crypto/compact.zig | 2 +- src/crypto/ecies.zig | 6 +- src/crypto/hash.zig | 6 +- src/crypto/secp256k1.zig | 36 ++-- src/crypto/signature.zig | 4 +- src/message/encrypted.zig | 26 +-- src/message/signed.zig | 24 +-- src/primitives/aescbc.zig | 4 +- src/primitives/aesgcm.zig | 16 +- src/primitives/base58.zig | 2 +- src/primitives/bip32.zig | 2 +- src/primitives/bip39.zig | 20 +- src/primitives/chainhash.zig | 4 +- src/primitives/drbg.zig | 8 +- src/primitives/ec.zig | 6 +- src/primitives/ecdsa.zig | 4 +- src/primitives/hex.zig | 2 +- src/primitives/keyshares.zig | 4 +- src/primitives/schnorr.zig | 2 +- src/primitives/symmetric.zig | 4 +- src/primitives/varint.zig | 2 +- src/script/engine.zig | 240 +++++++++++----------- src/script/interpreter.zig | 10 +- src/script/num.zig | 2 +- src/script/parser.zig | 8 +- src/script/templates/op_return.zig | 6 +- src/script/templates/p2pkh.zig | 10 +- src/script/templates/pushdrop.zig | 8 +- src/script/templates/r_puzzle.zig | 2 +- src/script/thread.zig | 8 +- src/spv/merkle_path.zig | 8 +- src/spv/verify.zig | 2 +- src/transaction/beef.zig | 6 +- src/transaction/builder.zig | 38 ++-- src/transaction/fees.zig | 4 +- src/transaction/output.zig | 2 +- src/transaction/preimage.zig | 30 +-- src/transaction/sighash.zig | 18 +- src/transaction/templates/p2pkh_spend.zig | 12 +- src/transaction/transaction.zig | 2 +- src/util.zig | 6 +- tests/external_coverage_notice.zig | 12 +- tests/go_corpus_accounting.zig | 11 +- tests/go_corpus_filtered_vectors.zig | 13 +- tests/go_exact_corpus_vectors.zig | 11 +- tests/go_meta_rows_vectors.zig | 11 +- tests/go_multisig_reference_vectors.zig | 11 +- tests/go_sigcheck_reference_vectors.zig | 11 +- tests/support/go_reference_harness.zig | 2 +- tests/support/go_script_harness.zig | 2 +- tests/template_reference_vectors.zig | 6 +- 59 files changed, 425 insertions(+), 336 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..32d8e43 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + zig: ["0.16.0", "master"] + steps: + - uses: actions/checkout@v5 + - uses: mlugg/setup-zig@v2 + with: + version: ${{ matrix.zig }} + - name: Build + run: zig build --summary all + - name: Test (ReleaseFast, UB checks enabled) + run: zig build test -Doptimize=ReleaseFast --summary all diff --git a/README.md b/README.md index dbda64c..4bdde3c 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Crypto, keys, script, transactions, SPV, BEEF, and broadcast. 27 BRC standards c ## Getting Started -**Requirements:** Zig `0.16.0` +**Requirements:** Zig `0.16.0` or newer — CI continuously verifies both the latest stable release and current master (`0.17` dev). Fetch the dependency: diff --git a/benchmarks/script_engine.zig b/benchmarks/script_engine.zig index b23f42f..afc5130 100644 --- a/benchmarks/script_engine.zig +++ b/benchmarks/script_engine.zig @@ -8,6 +8,8 @@ const engine = bsvz.script.engine; const iterations = 10_000; const fixture_allocator = std.heap.page_allocator; +const sep_line: [90]u8 = @splat('='); + fn bench(io: std.Io, comptime name: []const u8, comptime run_fn: fn (std.mem.Allocator) void) void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -41,7 +43,7 @@ const branching_locking = [_]u8{ 0x51, 0x63, 0x52, 0x67, 0x53, 0x68, 0x52, 0x9c const sha256_locking = [_]u8{ 0x20, -} ++ [_]u8{0xab} ** 32 ++ [_]u8{ +} ++ @as([32]u8, @splat(0xab)) ++ [_]u8{ 0xa8, 0x82, 0x01, 0x20, @@ -50,7 +52,7 @@ const sha256_locking = [_]u8{ const hash160_locking = [_]u8{ 0x14, -} ++ [_]u8{0xcd} ** 20 ++ [_]u8{ +} ++ @as([20]u8, @splat(0xcd)) ++ [_]u8{ 0xa9, 0x82, 0x01, 0x14, @@ -145,7 +147,7 @@ var go_reference_p2pkh_fixture_initialized = false; var go_reference_p2pkh_fixture: ReferencePrevoutFixture = undefined; fn initP2PKHFixture() void { - const key_bytes = [_]u8{0} ** 31 ++ [_]u8{1}; + const key_bytes = @as([31]u8, @splat(0)) ++ [_]u8{1}; const private_key = bsvz.crypto.PrivateKey.fromBytes(key_bytes) catch unreachable; const locking_script = Script.init(&p2pkh_locking); const previous_satoshis: i64 = 100_000; @@ -154,7 +156,7 @@ fn initP2PKHFixture() void { var outputs = fixture_allocator.alloc(bsvz.transaction.Output, 1) catch unreachable; inputs[0] = .{ - .previous_outpoint = .{ .txid = .{ .bytes = [_]u8{0xaa} ** 32 }, .index = 0 }, + .previous_outpoint = .{ .txid = .{ .bytes = @as([32]u8, @splat(0xaa)) }, .index = 0 }, .unlocking_script = Script.init(""), .sequence = 0xffff_ffff, }; @@ -343,7 +345,7 @@ pub fn main() !void { const io = threaded.io(); std.debug.print("\nbsvz script engine benchmarks ({d} iterations each)\n", .{iterations}); - std.debug.print("{s}\n", .{"=" ** 90}); + std.debug.print("{s}\n", .{sep_line}); bench(io, "arithmetic verify (2+3==5)", benchArithmetic); bench(io, "branching verify (if/else)", benchBranching); @@ -357,5 +359,5 @@ pub fn main() !void { bench(io, "P2PKH verify (synthetic fixture)", benchP2PKHVerify); bench(io, "P2PKH verify (Go reference tx)", benchGoReferenceP2PKHVerify); - std.debug.print("{s}\n", .{"=" ** 90}); + std.debug.print("{s}\n", .{sep_line}); } diff --git a/examples/prevout_trace_demo.zig b/examples/prevout_trace_demo.zig index 3c14bb8..1c88943 100644 --- a/examples/prevout_trace_demo.zig +++ b/examples/prevout_trace_demo.zig @@ -9,7 +9,7 @@ pub fn main() !void { .inputs = &[_]bsvz.transaction.Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = bsvz.script.Script.init(&[_]u8{}), diff --git a/src/broadcast/arc.zig b/src/broadcast/arc.zig index 19f2a2e..3c037b2 100644 --- a/src/broadcast/arc.zig +++ b/src/broadcast/arc.zig @@ -269,7 +269,7 @@ test "arc extended payload when all inputs have source" { .version = 1, .inputs = &.{.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{1} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(1)) }, .index = 0, }, .unlocking_script = .{ .bytes = &[_]u8{} }, diff --git a/src/compat/address.zig b/src/compat/address.zig index 5906739..0a00b29 100644 --- a/src/compat/address.zig +++ b/src/compat/address.zig @@ -68,7 +68,7 @@ test "p2pkh address encodes the all-zero mainnet vector" { const address = try encodeP2pkh( allocator, .mainnet, - .{ .bytes = [_]u8{0} ** 20 }, + .{ .bytes = @as([20]u8, @splat(0)) }, ); defer allocator.free(address); @@ -79,7 +79,7 @@ test "p2pkh address decode roundtrip preserves network and locking script" { const allocator = std.testing.allocator; const original = P2pkhAddress{ .network = .testnet, - .pubkey_hash = .{ .bytes = [_]u8{0x42} ** 20 }, + .pubkey_hash = .{ .bytes = @as([20]u8, @splat(0x42)) }, }; const encoded = try original.encode(allocator); @@ -95,7 +95,7 @@ test "p2pkh address decode roundtrip preserves network and locking script" { test "p2pkh address from compressed public key one matches the known vector" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -109,11 +109,11 @@ test "p2pkh address from compressed public key one matches the known vector" { test "p2pkh address decode rejects malformed payloads and prefixes" { const allocator = std.testing.allocator; - const short_payload = try primitives.base58.encodeCheck(allocator, &([_]u8{0x00} ++ ([_]u8{0x11} ** 19))); + const short_payload = try primitives.base58.encodeCheck(allocator, &([_]u8{0x00} ++ (@as([19]u8, @splat(0x11))))); defer allocator.free(short_payload); try std.testing.expectError(error.InvalidAddressPayload, decodeP2pkh(allocator, short_payload)); - const bad_prefix = try primitives.base58.encodeCheck(allocator, &([_]u8{0x05} ++ ([_]u8{0x22} ** 20))); + const bad_prefix = try primitives.base58.encodeCheck(allocator, &([_]u8{0x05} ++ (@as([20]u8, @splat(0x22))))); defer allocator.free(bad_prefix); try std.testing.expectError(error.InvalidNetworkPrefix, decodeP2pkh(allocator, bad_prefix)); diff --git a/src/compat/bsm.zig b/src/compat/bsm.zig index 3356daf..dc63471 100644 --- a/src/compat/bsm.zig +++ b/src/compat/bsm.zig @@ -22,7 +22,7 @@ fn appendPayload(list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator /// Double-SHA256 of the BSM-prefixed payload (`VarInt(len) || prefix || VarInt(len) || message`). pub fn messageDigestAlloc(allocator: std.mem.Allocator, message: []const u8) ![32]u8 { - var list = std.ArrayListUnmanaged(u8){}; + var list: std.ArrayListUnmanaged(u8) = .empty; errdefer list.deinit(allocator); try appendPayload(&list, allocator, message); const owned = try list.toOwnedSlice(allocator); @@ -70,7 +70,7 @@ pub fn verifyMessage( test "bsm sign / recover / verify roundtrip" { const allocator = std.testing.allocator; - var kb: [32]u8 = [_]u8{0} ** 32; + var kb: [32]u8 = @as([32]u8, @splat(0)); kb[31] = 1; const sk = try crypto.PrivateKey.fromBytes(kb); const message = "hello bsm"; diff --git a/src/compat/wif.zig b/src/compat/wif.zig index c899468..2572d9c 100644 --- a/src/compat/wif.zig +++ b/src/compat/wif.zig @@ -59,7 +59,7 @@ fn networkFromPrefix(prefix: u8) ?primitives.network.Network { test "wif compressed mainnet key one matches the known vector" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -81,7 +81,7 @@ test "wif compressed mainnet key one matches the known vector" { test "wif decode roundtrip preserves key bytes and compression flag" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0x42} ** 32; + var key_bytes = @as([32]u8, @splat(0x42)); key_bytes[0] = 0x01; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -96,10 +96,10 @@ test "wif decode roundtrip preserves key bytes and compression flag" { test "wif decode rejects malformed payloads, prefixes, and checksums" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; - const short_payload = try primitives.base58.encodeCheck(allocator, &([_]u8{0x80} ++ ([_]u8{0x00} ** 31))); + const short_payload = try primitives.base58.encodeCheck(allocator, &([_]u8{0x80} ++ (@as([31]u8, @splat(0x00))))); defer allocator.free(short_payload); try std.testing.expectError(error.InvalidWifPayload, decode(allocator, short_payload)); diff --git a/src/crypto/compact.zig b/src/crypto/compact.zig index 7069d08..71bd682 100644 --- a/src/crypto/compact.zig +++ b/src/crypto/compact.zig @@ -42,7 +42,7 @@ pub const RecoveredPubkey = struct { /// Double-SHA256 digest -> scalar e, matching OpenSSL / go-sdk `hashToInt` for 32-byte digests. fn hashToIntScalar(digest: [32]u8) [32]u8 { - var wide: [48]u8 = [_]u8{0} ** 48; + var wide: [48]u8 = @as([48]u8, @splat(0)); @memcpy(wide[wide.len - 32 ..], &digest); return SecpScalar.reduce48(wide, .big); } diff --git a/src/crypto/ecies.zig b/src/crypto/ecies.zig index 458492c..c8b0b7f 100644 --- a/src/crypto/ecies.zig +++ b/src/crypto/ecies.zig @@ -70,7 +70,7 @@ pub fn electrumEncryptAlloc( const cipher = try aescbc.aesCbcEncrypt(allocator, message, key_e, iv, false); defer allocator.free(cipher); - var prefix = std.ArrayListUnmanaged(u8){}; + var prefix: std.ArrayListUnmanaged(u8) = .empty; defer prefix.deinit(allocator); try prefix.appendSlice(allocator, magic); if (!no_key) { @@ -137,7 +137,7 @@ pub fn bitcoreEncryptAlloc( iv_in: ?[16]u8, ) Error![]u8 { const sender = from_priv orelse try randomPrivateKey(); - var iv: [16]u8 = iv_in orelse [_]u8{0} ** 16; + var iv: [16]u8 = iv_in orelse @as([16]u8, @splat(0)); const r_buf = try publicKeyBytes(sender); const p = try to_pub.toPoint().mul(sender.bytes); @@ -213,7 +213,7 @@ test "electrum encrypt/decrypt with explicit ephemeral key" { const pk = (try compat_wif.decode(allocator, wif)).private_key; const pk_pub = try pk.publicKey(); - var eph_sk = [_]u8{0} ** 32; + var eph_sk = @as([32]u8, @splat(0)); eph_sk[16] = 0x42; const eph = try secp.PrivateKey.fromBytes(eph_sk); diff --git a/src/crypto/hash.zig b/src/crypto/hash.zig index 6eb4031..12d19c5 100644 --- a/src/crypto/hash.zig +++ b/src/crypto/hash.zig @@ -52,7 +52,7 @@ pub const Hash160 = struct { bytes: [20]u8, pub fn zero() Hash160 { - return .{ .bytes = [_]u8{0} ** 20 }; + return .{ .bytes = @as([20]u8, @splat(0)) }; } pub fn eql(self: Hash160, other: Hash160) bool { @@ -64,7 +64,7 @@ pub const Hash256 = struct { bytes: [32]u8, pub fn zero() Hash256 { - return .{ .bytes = [_]u8{0} ** 32 }; + return .{ .bytes = @as([32]u8, @splat(0)) }; } pub fn eql(self: Hash256, other: Hash256) bool { @@ -125,7 +125,7 @@ fn ripemd160Hash(out: *[20]u8, data: []const u8) void { ripemd160ProcessBlock(block, &h0, &h1, &h2, &h3, &h4); } - var tail: [128]u8 = [_]u8{0} ** 128; + var tail: [128]u8 = @as([128]u8, @splat(0)); const remainder = data.len % 64; @memcpy(tail[0..remainder], data[full_blocks * 64 ..]); tail[remainder] = 0x80; diff --git a/src/crypto/secp256k1.zig b/src/crypto/secp256k1.zig index 832746f..1117479 100644 --- a/src/crypto/secp256k1.zig +++ b/src/crypto/secp256k1.zig @@ -68,7 +68,7 @@ pub const Point = struct { pub fn toCompressedSec1(self: Point) Sec1Bytes { var out = Sec1Bytes{ - .bytes = [_]u8{0} ** 65, + .bytes = @as([65]u8, @splat(0)), .len = 1, }; if (self.isIdentity()) { @@ -84,7 +84,7 @@ pub const Point = struct { pub fn toUncompressedSec1(self: Point) Sec1Bytes { var out = Sec1Bytes{ - .bytes = [_]u8{0} ** 65, + .bytes = @as([65]u8, @splat(0)), .len = 1, }; if (self.isIdentity()) { @@ -99,7 +99,7 @@ pub const Point = struct { } pub fn toRaw64(self: Point) [64]u8 { - var out = [_]u8{0} ** 64; + var out = @as([64]u8, @splat(0)); if (self.isIdentity()) return out; const affine = self.inner.affineCoordinates(); @@ -145,8 +145,8 @@ pub const Point = struct { pub fn affineBytes32(self: Point) AffineBytes32 { if (self.isIdentity()) { return .{ - .x = [_]u8{0} ** 32, - .y = [_]u8{0} ** 32, + .x = @as([32]u8, @splat(0)), + .y = @as([32]u8, @splat(0)), }; } @@ -377,14 +377,14 @@ fn normalizeLaxDerInt(raw: []const u8) Error![32]u8 { } if (bytes.len > 32) return error.InvalidEncoding; - var out = [_]u8{0} ** 32; + var out = @as([32]u8, @splat(0)); @memcpy(out[32 - bytes.len ..], bytes); if (std.mem.allEqual(u8, &out, 0)) return error.InvalidEncoding; return out; } test "public key derivation and sha256 sign/verify roundtrip" { - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try PrivateKey.fromBytes(key_bytes); @@ -416,13 +416,13 @@ test "relaxed der parser accepts padded integers that strict der rejects" { } test "digest verification helpers match expected truth values" { - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try PrivateKey.fromBytes(key_bytes); const public_key = try private_key.publicKey(); - const digest = [_]u8{0x42} ** 32; - const wrong_digest = [_]u8{0x24} ** 32; + const digest = @as([32]u8, @splat(0x42)); + const wrong_digest = @as([32]u8, @splat(0x24)); const sig = try private_key.signDigest256(digest); try std.testing.expect(try verifyDigest256Sec1(&public_key.bytes, digest, sig)); @@ -432,10 +432,10 @@ test "digest verification helpers match expected truth values" { } test "point sec1 and arithmetic wrap stdlib secp256k1" { - var scalar_one = [_]u8{0} ** 32; + var scalar_one = @as([32]u8, @splat(0)); scalar_one[31] = 1; - var scalar_two = [_]u8{0} ** 32; + var scalar_two = @as([32]u8, @splat(0)); scalar_two[31] = 2; const g = try Point.basePointMul(scalar_one); @@ -450,7 +450,7 @@ test "point sec1 and arithmetic wrap stdlib secp256k1" { } test "point raw64 and public key bridging" { - var scalar = [_]u8{0} ** 32; + var scalar = @as([32]u8, @splat(0)); scalar[31] = 1; const point = try Point.basePointMul(scalar); @@ -465,14 +465,14 @@ test "point raw64 and public key bridging" { try std.testing.expectEqualSlices(u8, &public_key.toUncompressedSec1(), point.toUncompressedSec1().slice()); try std.testing.expectEqualSlices(u8, raw[0..32], &x); try std.testing.expectEqualSlices(u8, raw[32..64], &y); - try std.testing.expectEqualSlices(u8, Point.identity().toCompressedSec1().slice(), (try point.mul([_]u8{0} ** 32)).toCompressedSec1().slice()); + try std.testing.expectEqualSlices(u8, Point.identity().toCompressedSec1().slice(), (try point.mul(@as([32]u8, @splat(0)))).toCompressedSec1().slice()); } test "point affine/raw/sec1 roundtrips stay aligned" { const scalars = [_]u8{ 1, 2, 3, 7, 42 }; for (scalars) |scalar_value| { - var scalar = [_]u8{0} ** 32; + var scalar = @as([32]u8, @splat(0)); scalar[31] = scalar_value; const point = try Point.basePointMul(scalar); @@ -509,9 +509,9 @@ test "point identity encodings roundtrip across public helpers" { try std.testing.expectEqual(@as(u8, 0x00), compressed.bytes[0]); try std.testing.expectEqual(@as(usize, 1), uncompressed.len); try std.testing.expectEqual(@as(u8, 0x00), uncompressed.bytes[0]); - try std.testing.expectEqualSlices(u8, &([_]u8{0} ** 32), &affine.x); - try std.testing.expectEqualSlices(u8, &([_]u8{0} ** 32), &affine.y); - try std.testing.expectEqualSlices(u8, &([_]u8{0} ** 64), &raw); + try std.testing.expectEqualSlices(u8, &(@as([32]u8, @splat(0))), &affine.x); + try std.testing.expectEqualSlices(u8, &(@as([32]u8, @splat(0))), &affine.y); + try std.testing.expectEqualSlices(u8, &(@as([64]u8, @splat(0))), &raw); try std.testing.expect((try Point.fromCompressedSec1(compressed.slice())).isIdentity()); try std.testing.expect((try Point.fromUncompressedSec1(uncompressed.slice())).isIdentity()); try std.testing.expectError(error.InvalidEncoding, Point.fromAffineBytes32(affine.x, affine.y)); diff --git a/src/crypto/signature.zig b/src/crypto/signature.zig index f2053d5..15c2733 100644 --- a/src/crypto/signature.zig +++ b/src/crypto/signature.zig @@ -10,7 +10,7 @@ pub const DerSignature = struct { if (der.len == 0 or der.len > max_der_signature_len) return error.InvalidEncoding; var out = DerSignature{ - .bytes = [_]u8{0} ** max_der_signature_len, + .bytes = @as([max_der_signature_len]u8, @splat(0)), .len = der.len, }; @memcpy(out.bytes[0..der.len], der); @@ -25,7 +25,7 @@ pub const DerSignature = struct { var buf: [Scheme.Signature.der_encoded_length_max]u8 = undefined; const der = sig.toDer(&buf); var out = DerSignature{ - .bytes = [_]u8{0} ** max_der_signature_len, + .bytes = @as([max_der_signature_len]u8, @splat(0)), .len = der.len, }; @memcpy(out.bytes[0..der.len], der); diff --git a/src/message/encrypted.zig b/src/message/encrypted.zig index b106f70..b716458 100644 --- a/src/message/encrypted.zig +++ b/src/message/encrypted.zig @@ -112,10 +112,10 @@ pub fn decryptAlloc(allocator: std.mem.Allocator, message: []const u8, recipient } test "BRC-78 ecdh encrypt vs decrypt paths" { - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient_priv = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient_priv = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient_priv.publicKey(); - var key_id: [32]u8 = [_]u8{42} ** 32; + var key_id: [32]u8 = @as([32]u8, @splat(42)); const allocator = std.testing.allocator; const invoice = try invoiceEncryptionAlloc(allocator, &key_id); defer allocator.free(invoice); @@ -132,10 +132,10 @@ test "BRC-78 ecdh encrypt vs decrypt paths" { } test "BRC-78 derived signing pub matches priv" { - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient_priv = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient_priv = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient_priv.publicKey(); - var key_id: [32]u8 = [_]u8{42} ** 32; + var key_id: [32]u8 = @as([32]u8, @splat(42)); const allocator = std.testing.allocator; const invoice = try invoiceEncryptionAlloc(allocator, &key_id); defer allocator.free(invoice); @@ -147,8 +147,8 @@ test "BRC-78 derived signing pub matches priv" { test "BRC-78 roundtrip" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient.publicKey(); const msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; @@ -163,8 +163,8 @@ test "BRC-78 roundtrip" { test "BRC-78 version mismatch" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient.publicKey(); const msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; @@ -180,9 +180,9 @@ test "BRC-78 version mismatch" { test "BRC-78 wrong recipient" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); - var wrong_s: [32]u8 = [_]u8{0} ** 32; + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); + var wrong_s: [32]u8 = @as([32]u8, @splat(0)); wrong_s[31] = 22; const wrong = try ec.PrivateKey.fromBytes(wrong_s); const recipient_pub = try recipient.publicKey(); diff --git a/src/message/signed.zig b/src/message/signed.zig index 65fcfdd..cc4f9fc 100644 --- a/src/message/signed.zig +++ b/src/message/signed.zig @@ -8,7 +8,7 @@ pub const version_bytes = [4]u8{ 0x42, 0x42, 0x33, 0x01 }; /// Placeholder private key used when signing/verifying for "anyone" (go `PrivateKeyFromBytes([]byte{1})`). pub fn anyonePrivateKey() !ec.PrivateKey { - var scalar: [32]u8 = [_]u8{0} ** 32; + var scalar: [32]u8 = @as([32]u8, @splat(0)); scalar[31] = 1; return ec.PrivateKey.fromBytes(scalar); } @@ -143,8 +143,8 @@ pub fn verify(message: []const u8, sig: []const u8, recipient: ?ec.PrivateKey) V test "BRC-77 sign/verify with recipient" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient.publicKey(); const msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; @@ -156,7 +156,7 @@ test "BRC-77 sign/verify with recipient" { test "BRC-77 sign/verify anyone" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); const msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; const sig = try signAlloc(allocator, &msg, sender, null); @@ -167,8 +167,8 @@ test "BRC-77 sign/verify anyone" { test "BRC-77 version mismatch" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient.publicKey(); const msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; @@ -184,8 +184,8 @@ test "BRC-77 version mismatch" { test "BRC-77 recipient required" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); const recipient_pub = try recipient.publicKey(); const msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; @@ -197,9 +197,9 @@ test "BRC-77 recipient required" { test "BRC-77 wrong recipient" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); - const recipient = try ec.PrivateKey.fromBytes([_]u8{21} ** 32); - var wrong_s: [32]u8 = [_]u8{0} ** 32; + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); + const recipient = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(21))); + var wrong_s: [32]u8 = @as([32]u8, @splat(0)); wrong_s[31] = 22; const wrong = try ec.PrivateKey.fromBytes(wrong_s); const recipient_pub = try recipient.publicKey(); @@ -213,7 +213,7 @@ test "BRC-77 wrong recipient" { test "BRC-77 tampered message" { const allocator = std.testing.allocator; - const sender = try ec.PrivateKey.fromBytes([_]u8{15} ** 32); + const sender = try ec.PrivateKey.fromBytes(@as([32]u8, @splat(15))); var msg = [_]u8{ 1, 2, 4, 8, 16, 32 }; const sig = try signAlloc(allocator, &msg, sender, null); diff --git a/src/primitives/aescbc.zig b/src/primitives/aescbc.zig index a324267..62cfbcb 100644 --- a/src/primitives/aescbc.zig +++ b/src/primitives/aescbc.zig @@ -133,8 +133,8 @@ fn decryptBlocks(ctx: anytype, cipher: []const u8, iv: []const u8, out: []u8) vo test "aes-cbc encrypt/decrypt roundtrip" { const allocator = std.testing.allocator; - const key = [_]u8{0x11} ** 32; - const iv = [_]u8{0x22} ** 16; + const key = @as([32]u8, @splat(0x11)); + const iv = @as([16]u8, @splat(0x22)); const msg = "bsvz aes cbc"; const enc = try aesCbcEncrypt(allocator, msg, &key, &iv, false); defer allocator.free(enc); diff --git a/src/primitives/aesgcm.zig b/src/primitives/aesgcm.zig index 49e34a1..132514c 100644 --- a/src/primitives/aesgcm.zig +++ b/src/primitives/aesgcm.zig @@ -109,7 +109,7 @@ fn aesGcmEncryptWith( const ctx = Aes.initEnc(key_bytes); var h: [16]u8 = undefined; - const zeros = [_]u8{0} ** 16; + const zeros = @as([16]u8, @splat(0)); ctx.encrypt(&h, &zeros); const j0 = computeJ0(h, nonce); @@ -142,7 +142,7 @@ fn aesGcmDecryptWith( const ctx = Aes.initEnc(key_bytes); var h: [16]u8 = undefined; - const zeros = [_]u8{0} ** 16; + const zeros = @as([16]u8, @splat(0)); ctx.encrypt(&h, &zeros); const j0 = computeJ0(h, nonce); @@ -164,7 +164,7 @@ fn aesGcmDecryptWith( fn computeJ0(h: [16]u8, nonce: []const u8) [16]u8 { if (nonce.len == 12) { - var out = [_]u8{0} ** 16; + var out = @as([16]u8, @splat(0)); @memcpy(out[0..12], nonce); std.mem.writeInt(u32, out[12..16], 1, .big); return out; @@ -175,7 +175,7 @@ fn computeJ0(h: [16]u8, nonce: []const u8) [16]u8 { var mac = Ghash.initForBlockCount(&h, block_count); mac.update(nonce); mac.pad(); - var final_block: [16]u8 = [_]u8{0} ** 16; + var final_block: [16]u8 = @as([16]u8, @splat(0)); std.mem.writeInt(u64, final_block[8..16], @as(u64, nonce.len) * 8, .big); mac.update(&final_block); var out: [16]u8 = undefined; @@ -212,8 +212,8 @@ fn inc32(block: *[16]u8) void { test "aes-gcm encrypt/decrypt roundtrip with 12-byte nonce" { const allocator = std.testing.allocator; - const key = [_]u8{0x11} ** 32; - const nonce = [_]u8{0x22} ** 12; + const key = @as([32]u8, @splat(0x11)); + const nonce = @as([12]u8, @splat(0x22)); const msg = "bsvz aesgcm"; const ad = "aad"; const enc = try aesGcmEncrypt(allocator, msg, &key, &nonce, ad); @@ -225,8 +225,8 @@ test "aes-gcm encrypt/decrypt roundtrip with 12-byte nonce" { test "aes-gcm encrypt/decrypt roundtrip with 32-byte nonce" { const allocator = std.testing.allocator; - const key = [_]u8{0x33} ** 32; - const nonce = [_]u8{0x44} ** 32; + const key = @as([32]u8, @splat(0x33)); + const nonce = @as([32]u8, @splat(0x44)); const msg = "bsvz aesgcm long nonce"; const enc = try aesGcmEncrypt(allocator, msg, &key, &nonce, ""); defer allocator.free(enc.ciphertext); diff --git a/src/primitives/base58.zig b/src/primitives/base58.zig index 58a2f18..042f4d5 100644 --- a/src/primitives/base58.zig +++ b/src/primitives/base58.zig @@ -140,7 +140,7 @@ test "base58 preserves leading zero bytes" { test "base58check encodes the all-zero p2pkh payload vector" { const allocator = std.testing.allocator; - const payload = [_]u8{0x00} ++ ([_]u8{0x00} ** 20); + const payload = [_]u8{0x00} ++ (@as([20]u8, @splat(0x00))); const encoded = try encodeCheck(allocator, &payload); defer allocator.free(encoded); diff --git a/src/primitives/bip32.zig b/src/primitives/bip32.zig index bdca005..cf2c717 100644 --- a/src/primitives/bip32.zig +++ b/src/primitives/bip32.zig @@ -372,6 +372,6 @@ test "bip32 errors" { try std.testing.expectError(error.DeriveHardFromPublic, xp.child(HardenedKeyStart)); try std.testing.expectError(error.NotPrivExtKey, xp.privateKey()); - try std.testing.expectError(error.InvalidSeedLen, newMaster(&[_]u8{0} ** 8, Versions.mainnet)); + try std.testing.expectError(error.InvalidSeedLen, newMaster(&@as([8]u8, @splat(0)), Versions.mainnet)); try std.testing.expectError(error.InvalidChecksum, parseAlloc(allocator, "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHx")); } diff --git a/src/primitives/bip39.zig b/src/primitives/bip39.zig index 4d661ab..cdfaffc 100644 --- a/src/primitives/bip39.zig +++ b/src/primitives/bip39.zig @@ -107,12 +107,12 @@ fn checksumShift(n_words: usize) u32 { } fn entropyFromWords(words: *[24][]const u8, nw: usize, out: *[32]u8) Error!usize { - var b: EntropyInt = 0; + var b: u512 = 0; for (0..nw) |i| { const idx = wordIndex(words[i]) orelse return error.InvalidMnemonic; - b = b * @as(EntropyInt, 2048) + @as(EntropyInt, idx); + b = b * @as(u512, 2048) + @as(u512, idx); } - const mask: EntropyInt = checksumMask(nw); + const mask: u512 = checksumMask(nw); const cs = b & mask; const b_ent = b / (mask + 1); @@ -166,7 +166,7 @@ pub fn newMnemonic(allocator: std.mem.Allocator, entropy: []const u8) Error![]u8 return s; } -fn addChecksumU512(entropy: []const u8) EntropyInt { +fn addChecksumU512(entropy: []const u8) u512 { var bits = beBytesToEntropyInt(entropy); const checksum_bits: u32 = @intCast(entropy.len / 4); var hash: [32]u8 = undefined; @@ -180,14 +180,14 @@ fn addChecksumU512(entropy: []const u8) EntropyInt { return bits; } -fn beBytesToEntropyInt(s: []const u8) EntropyInt { - var x: EntropyInt = 0; +fn beBytesToEntropyInt(s: []const u8) u512 { + var x: u512 = 0; for (s) |b| x = (x << 8) | b; return x; } /// Writes minimal big-endian `x` into `out`, left-padded with zeros to `pad_len` (Go `padByteSlice`). -fn u512ToBePadded(x: EntropyInt, out: []u8, pad_len: usize) []const u8 { +fn u512ToBePadded(x: u512, out: []u8, pad_len: usize) []const u8 { std.debug.assert(out.len >= pad_len); var tmp: [64]u8 = undefined; const minimal = u512ToBeMinimal(x, &tmp); @@ -196,7 +196,7 @@ fn u512ToBePadded(x: EntropyInt, out: []u8, pad_len: usize) []const u8 { return out[0..pad_len]; } -fn u512ToBeMinimal(x: EntropyInt, stack: *[64]u8) []const u8 { +fn u512ToBeMinimal(x: u512, stack: *[64]u8) []const u8 { if (x == 0) { stack[63] = 0; return stack[63..64]; @@ -250,7 +250,7 @@ pub fn mnemonicToByteArrayAlloc(allocator: std.mem.Allocator, mnemonic: []const return out; } -const EntropyInt = std.meta.Int(.unsigned, 512); +const EntropyInt = u512; test "bip39 official vectors (entropy, mnemonic, seed) passphrase TREZOR" { const allocator = std.testing.allocator; @@ -447,7 +447,7 @@ test "bip39 checksum errors match go-sdk cases" { test "bip39 newMnemonic rejects bad entropy length" { const allocator = std.testing.allocator; try std.testing.expectError(error.EntropyLengthInvalid, newMnemonic(allocator, &.{})); - var bad17: [17]u8 = .{0} ** 17; + var bad17: [17]u8 = @splat(0); try std.testing.expectError(error.EntropyLengthInvalid, newMnemonic(allocator, &bad17)); } diff --git a/src/primitives/chainhash.zig b/src/primitives/chainhash.zig index 737f2a6..a2f701c 100644 --- a/src/primitives/chainhash.zig +++ b/src/primitives/chainhash.zig @@ -14,7 +14,7 @@ pub const Hash = struct { bytes: [HashSize]u8, pub fn zero() Hash { - return .{ .bytes = [_]u8{0} ** HashSize }; + return .{ .bytes = @as([HashSize]u8, @splat(0)) }; } pub fn eql(self: Hash, other: Hash) bool { @@ -71,7 +71,7 @@ pub fn decode(dst: *Hash, src: []const u8) DecodeError!void { } const decoded_len = src_len / 2; - var reversed: [HashSize]u8 = [_]u8{0} ** HashSize; + var reversed: [HashSize]u8 = @as([HashSize]u8, @splat(0)); const out_slice = reversed[HashSize - decoded_len .. HashSize]; if (std.fmt.hexToBytes(out_slice, buf[0..src_len])) |_| {} else |_| { return error.InvalidHex; diff --git a/src/primitives/drbg.zig b/src/primitives/drbg.zig index 19d8de7..7a7fae9 100644 --- a/src/primitives/drbg.zig +++ b/src/primitives/drbg.zig @@ -17,8 +17,8 @@ pub const DRBG = struct { if (entropy.len < 32) return error.NotEnoughEntropy; var drbg = DRBG{ .allocator = allocator, - .k = [_]u8{0} ** 32, - .v = [_]u8{0x01} ** 32, + .k = @as([32]u8, @splat(0)), + .v = @as([32]u8, @splat(0x01)), .reseed_counter = 1, }; var seed_buf = try std.ArrayList(u8).initCapacity(allocator, entropy.len + nonce.len); @@ -77,8 +77,8 @@ pub const DRBG = struct { }; test "drbg generate length" { - var entropy = [_]u8{0x01} ** 32; - var nonce = [_]u8{0x02} ** 16; + var entropy = @as([32]u8, @splat(0x01)); + var nonce = @as([16]u8, @splat(0x02)); var d = try DRBG.init(&entropy, &nonce, std.testing.allocator); const out = try d.generate(std.testing.allocator, 64); defer std.testing.allocator.free(out); diff --git a/src/primitives/ec.zig b/src/primitives/ec.zig index 2abf213..6f099e6 100644 --- a/src/primitives/ec.zig +++ b/src/primitives/ec.zig @@ -372,7 +372,7 @@ pub fn privateKeyFromBackupShares( } fn integrityTag(key: PrivateKey) [8]u8 { - const pub_key = key.publicKey() catch return [_]u8{0} ** 8; + const pub_key = key.publicKey() catch return @as([8]u8, @splat(0)); const compressed = pub_key.toCompressedSec1(); const digest = crypto_hash.hash160(&compressed).bytes; var out: [8]u8 = undefined; @@ -383,7 +383,7 @@ fn integrityTag(key: PrivateKey) [8]u8 { } fn bytesToU256(bytes: []const u8) u256 { - var buf: [32]u8 = [_]u8{0} ** 32; + var buf: [32]u8 = @as([32]u8, @splat(0)); if (bytes.len > 32) { @memcpy(buf[0..32], bytes[bytes.len - 32 ..]); } else { @@ -446,7 +446,7 @@ test "deriveSharedSecret is symmetric" { test "secp256k1 params match base point" { const params = Secp256k1.params(); try std.testing.expect(Secp256k1.isOnCurve(params.gx, params.gy)); - var scalar_one = [_]u8{0} ** 32; + var scalar_one = @as([32]u8, @splat(0)); scalar_one[31] = 1; const base = try Secp256k1.scalarBaseMult(scalar_one); try std.testing.expectEqualSlices(u8, ¶ms.gx, &base.x); diff --git a/src/primitives/ecdsa.zig b/src/primitives/ecdsa.zig index 682e798..420dfda 100644 --- a/src/primitives/ecdsa.zig +++ b/src/primitives/ecdsa.zig @@ -69,8 +69,8 @@ const curve_n = hex32("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd const curve_half_n = hex32("7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"); test "signature DER roundtrip and low-S normalization" { - const msg_digest = [_]u8{0x11} ** 32; - var key_bytes = [_]u8{0} ** 32; + const msg_digest = @as([32]u8, @splat(0x11)); + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const priv = try secp256k1.PrivateKey.fromBytes(key_bytes); const pub_key = try priv.publicKey(); diff --git a/src/primitives/hex.zig b/src/primitives/hex.zig index ae9275d..408d0fe 100644 --- a/src/primitives/hex.zig +++ b/src/primitives/hex.zig @@ -43,7 +43,7 @@ pub fn decode(allocator: std.mem.Allocator, text: []const u8) ![]u8 { const nibble_table = buildNibbleTable(); fn buildNibbleTable() [256]u8 { - var table = [_]u8{0xff} ** 256; + var table = @as([256]u8, @splat(0xff)); for ('0'..'9' + 1) |char| table[char] = @intCast(char - '0'); for ('a'..'f' + 1) |char| table[char] = @intCast(char - 'a' + 10); for ('A'..'F' + 1) |char| table[char] = @intCast(char - 'A' + 10); diff --git a/src/primitives/keyshares.zig b/src/primitives/keyshares.zig index 42eb1b6..40fa1b3 100644 --- a/src/primitives/keyshares.zig +++ b/src/primitives/keyshares.zig @@ -97,7 +97,7 @@ pub const KeyShares = struct { errdefer allocator.free(points); var threshold: usize = 0; - var integrity: [8]u8 = [_]u8{0} ** 8; + var integrity: [8]u8 = @as([8]u8, @splat(0)); for (shares, 0..) |share, idx| { var it = std.mem.splitScalar(u8, share, '.'); @@ -127,7 +127,7 @@ pub const KeyShares = struct { }; fn bytesToU256(bytes: []const u8) u256 { - var buf: [32]u8 = [_]u8{0} ** 32; + var buf: [32]u8 = @as([32]u8, @splat(0)); if (bytes.len > 32) { @memcpy(buf[0..32], bytes[bytes.len - 32 ..]); } else if (bytes.len > 0) { diff --git a/src/primitives/schnorr.zig b/src/primitives/schnorr.zig index 55b8b52..a46873c 100644 --- a/src/primitives/schnorr.zig +++ b/src/primitives/schnorr.zig @@ -85,7 +85,7 @@ fn computeChallenge( } fn reduceScalar(digest: [32]u8) Scalar { - var reduced = [_]u8{0} ** 48; + var reduced = @as([48]u8, @splat(0)); @memcpy(reduced[reduced.len - digest.len ..], &digest); return Scalar.fromBytes48(reduced, .big); } diff --git a/src/primitives/symmetric.zig b/src/primitives/symmetric.zig index 1aae1ed..b3d0e6f 100644 --- a/src/primitives/symmetric.zig +++ b/src/primitives/symmetric.zig @@ -10,7 +10,7 @@ pub const SymmetricKey = struct { key: [32]u8, pub fn newFromBytes(bytes: []const u8) SymmetricKey { - var out = [_]u8{0} ** 32; + var out = @as([32]u8, @splat(0)); if (bytes.len >= 32) { @memcpy(&out, bytes[bytes.len - 32 ..]); } else { @@ -20,7 +20,7 @@ pub const SymmetricKey = struct { } pub fn newFromRandom() SymmetricKey { - var out = [_]u8{0} ** 32; + var out = @as([32]u8, @splat(0)); util.randomBytes(&out); return .{ .key = out }; } diff --git a/src/primitives/varint.zig b/src/primitives/varint.zig index a10cd7d..c365c97 100644 --- a/src/primitives/varint.zig +++ b/src/primitives/varint.zig @@ -96,7 +96,7 @@ test "varint encode and parse roundtrip across widths" { const values = [_]u64{ 0, 0xfc, 0xfd, 0xffff, 0x1_0000, 0xffff_ffff, 0x1_0000_0000 }; for (values) |value| { - var buf: [9]u8 = [_]u8{0} ** 9; + var buf: [9]u8 = @as([9]u8, @splat(0)); const len = try VarInt.encodeInto(&buf, value); const parsed = try VarInt.parse(buf[0..len]); diff --git a/src/script/engine.zig b/src/script/engine.zig index 6dc09b8..c46a9db 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -2254,9 +2254,9 @@ test "engine stack opcodes fail at the exact underflow boundary" { test "engine verifies 2-of-2 checksig ordering with checkmultisig" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_b[31] = 2; const private_key_a = try crypto.PrivateKey.fromBytes(key_bytes_a); @@ -2272,7 +2272,7 @@ test "engine verifies 2-of-2 checksig ordering with checkmultisig" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x55} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x55)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -2338,9 +2338,9 @@ test "engine verifies 2-of-2 checksig ordering with checkmultisig" { test "engine checkmultisig exits early before touching later invalid pubkeys" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_b[31] = 2; const private_key_a = try crypto.PrivateKey.fromBytes(key_bytes_a); @@ -2348,7 +2348,7 @@ test "engine checkmultisig exits early before touching later invalid pubkeys" { const public_key_a = try private_key_a.publicKey(); _ = try private_key_b.publicKey(); - var invalid_pubkey = [_]u8{0} ** 33; + var invalid_pubkey = @as([33]u8, @splat(0)); invalid_pubkey[0] = 0x05; const locking_script_bytes = [_]u8{ @@ -2367,7 +2367,7 @@ test "engine checkmultisig exits early before touching later invalid pubkeys" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x56} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x56)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -2419,8 +2419,8 @@ test "engine checkmultisig exits early before touching later invalid pubkeys" { test "engine checkmultisig errors on the first checked invalid pubkey under strict policy" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; @@ -2429,7 +2429,7 @@ test "engine checkmultisig errors on the first checked invalid pubkey under stri const public_key_a = try private_key_a.publicKey(); _ = try private_key_b.publicKey(); - var invalid_pubkey = [_]u8{0} ** 33; + var invalid_pubkey = @as([33]u8, @splat(0)); invalid_pubkey[0] = 0x05; const locking_script_bytes = [_]u8{ @@ -2448,7 +2448,7 @@ test "engine checkmultisig errors on the first checked invalid pubkey under stri .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x57} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x57)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -2503,8 +2503,8 @@ test "engine checkmultisig errors on the first checked invalid pubkey under stri test "engine checkmultisig errors on the first checked malformed signature under strict policy" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; @@ -2521,7 +2521,7 @@ test "engine checkmultisig errors on the first checked malformed signature under .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x58} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x58)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -2580,7 +2580,7 @@ test "engine checkmultisig errors on the first checked malformed signature under test "engine checkmultisig not turns a malformed signature into true without dersig" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -2601,7 +2601,7 @@ test "engine checkmultisig not turns a malformed signature into true without der .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x58} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x58)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -2655,7 +2655,7 @@ test "engine checkmultisig not turns a malformed signature into true without der test "engine checkmultisig not treats an empty signature as false even with dersig" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -2676,7 +2676,7 @@ test "engine checkmultisig not treats an empty signature as false even with ders .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x59} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x59)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -2720,7 +2720,7 @@ test "engine checkmultisig not treats an empty signature as false even with ders test "engine checkmultisig treats a malformed signature as false without dersig" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -2740,7 +2740,7 @@ test "engine checkmultisig treats a malformed signature as false without dersig" .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x5a} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x5a)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -2794,7 +2794,7 @@ test "engine checkmultisig treats a malformed signature as false without dersig" test "engine checkmultisig treats an empty signature as false even with dersig" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -2814,7 +2814,7 @@ test "engine checkmultisig treats an empty signature as false even with dersig" .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x5b} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x5b)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -2858,7 +2858,7 @@ test "engine checkmultisig treats an empty signature as false even with dersig" test "engine checkmultisig ignores later hybrid pubkeys when an earlier key already satisfies the signature" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -2885,7 +2885,7 @@ test "engine checkmultisig ignores later hybrid pubkeys when an earlier key alre .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x59} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x59)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -2930,7 +2930,7 @@ test "engine checkmultisig ignores later hybrid pubkeys when an earlier key alre test "engine checkmultisig errors on the first checked hybrid pubkey under strict policy" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -2957,7 +2957,7 @@ test "engine checkmultisig errors on the first checked hybrid pubkey under stric .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x5a} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x5a)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3002,7 +3002,7 @@ test "engine checkmultisig errors on the first checked hybrid pubkey under stric test "engine checkmultisig rejects illegal forkid under legacy strict policy" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3022,7 +3022,7 @@ test "engine checkmultisig rejects illegal forkid under legacy strict policy" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x5b} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x5b)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3076,7 +3076,7 @@ test "engine checkmultisig rejects illegal forkid under legacy strict policy" { test "engine checkmultisig not accepts a forkid signature when forkid mode is enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3097,7 +3097,7 @@ test "engine checkmultisig not accepts a forkid signature when forkid mode is en .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x5c} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x5c)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3153,7 +3153,7 @@ test "engine checkmultisig not accepts a forkid signature when forkid mode is en test "engine checkmultisig surfaces malformed signature before ordinary 2-of-3 failure" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3177,7 +3177,7 @@ test "engine checkmultisig surfaces malformed signature before ordinary 2-of-3 f .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x5c} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x5c)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3237,7 +3237,7 @@ test "engine checkmultisig surfaces malformed signature before ordinary 2-of-3 f test "engine legacy checksig removes pushed signature copies from script code when legacy mode is enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3256,7 +3256,7 @@ test "engine legacy checksig removes pushed signature copies from script code wh .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x77} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x77)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3313,7 +3313,7 @@ test "engine legacy checksig removes pushed signature copies from script code wh test "engine enforces NULLDUMMY for checkmultisig when enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3333,7 +3333,7 @@ test "engine enforces NULLDUMMY for checkmultisig when enabled" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x88} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x88)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3393,7 +3393,7 @@ test "engine enforces NULLDUMMY for checkmultisig when enabled" { test "engine multisig nullfail only trips on non-empty failing signatures" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3413,7 +3413,7 @@ test "engine multisig nullfail only trips on non-empty failing signatures" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x91} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x91)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3487,8 +3487,8 @@ test "engine multisig nullfail only trips on non-empty failing signatures" { test "engine multisig nullfail scans later signatures after checkmultisig-not failure" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; @@ -3514,7 +3514,7 @@ test "engine multisig nullfail scans later signatures after checkmultisig-not fa .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x92} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x92)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3574,8 +3574,8 @@ test "engine multisig nullfail scans later signatures after checkmultisig-not fa test "engine multisig nullfail ignores a nonzero dummy when nulldummy is disabled" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; @@ -3601,7 +3601,7 @@ test "engine multisig nullfail ignores a nonzero dummy when nulldummy is disable .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x93} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x93)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3641,7 +3641,7 @@ test "engine multisig nullfail ignores a nonzero dummy when nulldummy is disable test "engine multisig nulldummy takes precedence over nullfail" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3661,7 +3661,7 @@ test "engine multisig nulldummy takes precedence over nullfail" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x92} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x92)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3715,7 +3715,7 @@ test "engine multisig nulldummy takes precedence over nullfail" { test "engine checkmultisig not ignores nonzero dummy under nullfail when nulldummy is disabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3735,7 +3735,7 @@ test "engine checkmultisig not ignores nonzero dummy under nullfail when nulldum .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x94} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x94)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -3784,7 +3784,7 @@ test "engine checkmultisig not ignores nonzero dummy under nullfail when nulldum test "engine checkmultisig not reports nullfail before false but after nulldummy precedence" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -3804,7 +3804,7 @@ test "engine checkmultisig not reports nullfail before false but after nulldummy .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x95} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x95)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4340,7 +4340,7 @@ test "engine can disable re-enabled BSV opcodes through flags" { test "engine verifies p2pkh end to end through checksig" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4354,7 +4354,7 @@ test "engine verifies p2pkh end to end through checksig" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x33} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x33)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4409,7 +4409,7 @@ test "engine verifies p2pkh end to end through checksig" { test "engine treats malformed pubkeys as false unless strict pubkey policy is enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4424,7 +4424,7 @@ test "engine treats malformed pubkeys as false unless strict pubkey policy is en .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x22} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x22)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4483,7 +4483,7 @@ test "engine treats malformed pubkeys as false unless strict pubkey policy is en test "engine treats malformed DER signatures as false unless DER policy is enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4498,7 +4498,7 @@ test "engine treats malformed DER signatures as false unless DER policy is enabl .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x23} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x23)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4557,7 +4557,7 @@ test "engine treats malformed DER signatures as false unless DER policy is enabl test "engine checksig accepts a multi-byte sighash encoding when dersig is disabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4571,7 +4571,7 @@ test "engine checksig accepts a multi-byte sighash encoding when dersig is disab .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x23} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x23)) }, .index = 2, }, .unlocking_script = .{ .bytes = "" }, @@ -4642,7 +4642,7 @@ test "engine checksig accepts a multi-byte sighash encoding when dersig is disab test "engine checksig accepts a valid hybrid pubkey when strict encoding is disabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4662,7 +4662,7 @@ test "engine checksig accepts a valid hybrid pubkey when strict encoding is disa .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x24} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x24)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4726,7 +4726,7 @@ test "engine checksig accepts a valid hybrid pubkey when strict encoding is disa test "engine checksig not treats an invalid hybrid pubkey as false unless strict encoding is enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4747,7 +4747,7 @@ test "engine checksig not treats an invalid hybrid pubkey as false unless strict .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x25} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x25)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4823,7 +4823,7 @@ test "engine rejects missing forkid when forkid mode is enabled" { test "engine checksig not accepts a forkid signature when forkid mode is enabled" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -4838,7 +4838,7 @@ test "engine checksig not accepts a forkid signature when forkid mode is enabled .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x26} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x26)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4905,7 +4905,7 @@ test "engine checksig not matches go malformed-signature dersig matrix" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x27} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x27)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -4927,22 +4927,22 @@ test "engine checksig not matches go malformed-signature dersig matrix" { }{ .{ .name = "overly long signature", - .payload = &([_]u8{0} ** 74), + .payload = &(@as([74]u8, @splat(0))), }, .{ .name = "missing s", .payload = &[_]u8{ 0x30, 0x22, 0x02, 0x20, - } ++ ([_]u8{0x00} ** 32), + } ++ (@as([32]u8, @splat(0x00))), }, .{ .name = "non-integer r", .payload = &[_]u8{ 0x30, 0x24, 0x03, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x02, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x01, }, }, @@ -4951,7 +4951,7 @@ test "engine checksig not matches go malformed-signature dersig matrix" { .payload = &[_]u8{ 0x30, 0x14, 0x02, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x02, 0x00, 0x01, }, }, @@ -4960,9 +4960,9 @@ test "engine checksig not matches go malformed-signature dersig matrix" { .payload = &[_]u8{ 0x30, 0x24, 0x02, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x02, 0x10, 0x87, - } ++ ([_]u8{0x77} ** 15) ++ [_]u8{ + } ++ (@as([15]u8, @splat(0x77))) ++ [_]u8{ 0x01, }, }, @@ -4971,9 +4971,9 @@ test "engine checksig not matches go malformed-signature dersig matrix" { .payload = &[_]u8{ 0x30, 0x24, 0x02, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x02, 0x0a, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x01, }, }, @@ -4982,9 +4982,9 @@ test "engine checksig not matches go malformed-signature dersig matrix" { .payload = &[_]u8{ 0x30, 0x24, 0x02, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x03, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x01, }, }, @@ -4994,7 +4994,7 @@ test "engine checksig not matches go malformed-signature dersig matrix" { 0x30, 0x14, 0x02, 0x00, 0x02, 0x10, - } ++ ([_]u8{0x77} ** 16) ++ [_]u8{ + } ++ (@as([16]u8, @splat(0x77))) ++ [_]u8{ 0x01, }, }, @@ -5045,7 +5045,7 @@ test "engine checksig not matches go invalid sighash-type row in legacy mode" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x28} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x28)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -5278,7 +5278,7 @@ test "engine can enforce low-S policy on DER signatures" { test "engine checkmultisig not enforces low-S policy" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -5299,7 +5299,7 @@ test "engine checkmultisig not enforces low-S policy" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x71} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x71)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -5384,7 +5384,7 @@ test "engine checkmultisig not enforces low-S policy" { test "engine honors op_codeseparator in checksig subscript" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -5403,7 +5403,7 @@ test "engine honors op_codeseparator in checksig subscript" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x44} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x44)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -5441,7 +5441,7 @@ test "engine honors op_codeseparator in checksig subscript" { test "engine ignores codeseparator in an unexecuted legacy branch" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -5473,7 +5473,7 @@ test "engine ignores codeseparator in an unexecuted legacy branch" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x54} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x54)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -5517,9 +5517,9 @@ test "engine ignores codeseparator in an unexecuted legacy branch" { test "engine honors chained legacy codeseparator signing boundaries" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; - var key_bytes_c = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); + var key_bytes_c = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; key_bytes_c[31] = 3; @@ -5579,7 +5579,7 @@ test "engine honors chained legacy codeseparator signing boundaries" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x63} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x63)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -5674,9 +5674,9 @@ test "engine honors chained legacy codeseparator signing boundaries" { test "engine codeseparator wrong final signature yields a clean false result" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; - var key_bytes_c = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); + var key_bytes_c = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; key_bytes_c[31] = 3; @@ -5730,7 +5730,7 @@ test "engine codeseparator wrong final signature yields a clean false result" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x64} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x64)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -5801,9 +5801,9 @@ test "engine codeseparator wrong final signature yields a clean false result" { test "engine codeseparator wrong middle signature fails at checksigverify" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; - var key_bytes_c = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); + var key_bytes_c = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; key_bytes_c[31] = 3; @@ -5854,7 +5854,7 @@ test "engine codeseparator wrong middle signature fails at checksigverify" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x65} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x65)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -5921,9 +5921,9 @@ test "engine codeseparator wrong middle signature fails at checksigverify" { test "engine codeseparator can ignore a leading verified prelude in legacy mode" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; - var key_bytes_c = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); + var key_bytes_c = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; key_bytes_c[31] = 3; @@ -5986,7 +5986,7 @@ test "engine codeseparator can ignore a leading verified prelude in legacy mode" .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x66} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x66)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -6053,9 +6053,9 @@ test "engine codeseparator can ignore a leading verified prelude in legacy mode" test "engine codeseparator can isolate middle prelude to the final signature in legacy mode" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; - var key_bytes_c = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); + var key_bytes_c = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; key_bytes_c[31] = 3; @@ -6120,7 +6120,7 @@ test "engine codeseparator can isolate middle prelude to the final signature in .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x67} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x67)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -6187,9 +6187,9 @@ test "engine codeseparator can isolate middle prelude to the final signature in test "engine codeseparator wrong first signature fails at the first checksigverify" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; - var key_bytes_b = [_]u8{0} ** 32; - var key_bytes_c = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); + var key_bytes_b = @as([32]u8, @splat(0)); + var key_bytes_c = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; key_bytes_b[31] = 2; key_bytes_c[31] = 3; @@ -6237,7 +6237,7 @@ test "engine codeseparator wrong first signature fails at the first checksigveri .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x68} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x68)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -6410,9 +6410,9 @@ test "engine multisig uses per-signature scriptCode normalization" { test "engine verifies checkmultisig through an active codeseparator in legacy and forkid modes" { const allocator = std.testing.allocator; - var key_bytes_a = [_]u8{0} ** 32; + var key_bytes_a = @as([32]u8, @splat(0)); key_bytes_a[31] = 1; - var key_bytes_b = [_]u8{0} ** 32; + var key_bytes_b = @as([32]u8, @splat(0)); key_bytes_b[31] = 2; const private_key_a = try crypto.PrivateKey.fromBytes(key_bytes_a); @@ -6430,7 +6430,7 @@ test "engine verifies checkmultisig through an active codeseparator in legacy an .inputs = &[_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x66} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x66)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -6518,7 +6518,7 @@ test "engine verifies checkmultisig through an active codeseparator in legacy an test "engine treats equivalent pushdata forms equally at 75-byte and 255-byte boundaries" { const allocator = std.testing.allocator; - var data_75 = [_]u8{0x11} ** 75; + var data_75 = @as([75]u8, @splat(0x11)); var script_75 = try allocator.alloc(u8, 1 + 1 + data_75.len + 1 + data_75.len + 1); defer allocator.free(script_75); var cursor_75: usize = 0; @@ -6540,7 +6540,7 @@ test "engine treats equivalent pushdata forms equally at 75-byte and 255-byte bo defer result_75.deinit(allocator); try std.testing.expect(result_75.success); - var data_255 = [_]u8{0x22} ** 255; + var data_255 = @as([255]u8, @splat(0x22)); var script_255 = try allocator.alloc(u8, 3 + data_255.len + 2 + data_255.len + 1); defer allocator.free(script_255); var cursor_255: usize = 0; @@ -6571,7 +6571,7 @@ test "engine enforces active checklocktimeverify semantics in legacy reference m var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x01} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x01)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -6627,7 +6627,7 @@ test "engine enforces active checksequenceverify semantics in legacy reference m var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x02} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x02)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -6683,7 +6683,7 @@ test "engine rejects negative checklocktimeverify operands" { var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x03} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x03)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -6736,7 +6736,7 @@ test "engine enforces locktime type matching and finalized-input checks for chec var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x04} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x04)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -6806,7 +6806,7 @@ test "engine honors disabled-bit and version or type edge cases for checksequenc var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x05} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x05)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -6899,7 +6899,7 @@ test "engine rejects negative checksequenceverify operands" { var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x06} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x06)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -6942,7 +6942,7 @@ test "engine applies minimal-data rules to active checklocktimeverify operands" var inputs = [_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x07} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x07)) }, .index = 0, }, .unlocking_script = Script.init(""), diff --git a/src/script/interpreter.zig b/src/script/interpreter.zig index 5d4e35c..ce95657 100644 --- a/src/script/interpreter.zig +++ b/src/script/interpreter.zig @@ -116,7 +116,7 @@ test "interpreter verifyDetailed exposes structured false results" { .inputs = &[_]Input{ .{ .previous_outpoint = OutPoint{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = Script.init(&[_]u8{}), @@ -156,7 +156,7 @@ test "interpreter verifyOutcome exposes compact false results" { .inputs = &[_]Input{ .{ .previous_outpoint = OutPoint{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = Script.init(&[_]u8{}), @@ -191,7 +191,7 @@ test "interpreter verifyPrevoutDetailed exposes structured false results" { .inputs = &[_]Input{ .{ .previous_outpoint = OutPoint{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = Script.init(&[_]u8{}), @@ -230,7 +230,7 @@ test "interpreter verifyPrevoutOutcome exposes compact false results" { .inputs = &[_]Input{ .{ .previous_outpoint = OutPoint{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = Script.init(&[_]u8{}), @@ -275,7 +275,7 @@ test "interpreter verifyPrevout can explicitly execute legacy P2SH redeem script .inputs = &[_]Input{ .{ .previous_outpoint = OutPoint{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 0, }, .unlocking_script = Script.init(&unlocking_script_bytes), diff --git a/src/script/num.zig b/src/script/num.zig index f9242e5..6e28150 100644 --- a/src/script/num.zig +++ b/src/script/num.zig @@ -292,7 +292,7 @@ pub const ScriptNum = union(enum) { fn encodeMagnitudeUnsigned(allocator: std.mem.Allocator, magnitude_value: u128) ![]u8 { var magnitude = magnitude_value; - var tmp: [16]u8 = [_]u8{0} ** 16; + var tmp: [16]u8 = @as([16]u8, @splat(0)); var len: usize = 0; while (magnitude != 0) { tmp[len] = @truncate(magnitude & 0xff); diff --git a/src/script/parser.zig b/src/script/parser.zig index 2f8ec63..e445cf4 100644 --- a/src/script/parser.zig +++ b/src/script/parser.zig @@ -496,10 +496,10 @@ test "parser rejects malformed pushdata length prefixes" { test "parser roundtrips pushdata boundary encodings" { const allocator = std.testing.allocator; - const direct_75 = &[_]u8{0x11} ** 75; - const pushdata1_76 = &[_]u8{0x22} ** 76; - const pushdata1_255 = &[_]u8{0x33} ** 255; - const pushdata2_256 = &[_]u8{0x44} ** 256; + const direct_75 = &@as([75]u8, @splat(0x11)); + const pushdata1_76 = &@as([76]u8, @splat(0x22)); + const pushdata1_255 = &@as([255]u8, @splat(0x33)); + const pushdata2_256 = &@as([256]u8, @splat(0x44)); const script = Script.init( &[_]u8{75} ++ direct_75 ++ diff --git a/src/script/templates/op_return.zig b/src/script/templates/op_return.zig index 18fb340..01bef5c 100644 --- a/src/script/templates/op_return.zig +++ b/src/script/templates/op_return.zig @@ -51,17 +51,17 @@ test "op_return encode handles zero-length and max direct pushes" { defer allocator.free(empty); try std.testing.expectEqualSlices(u8, &[_]u8{ 0x6a, 0x00 }, empty); - const max_push = try encode(allocator, &([_]u8{0x42} ** 75)); + const max_push = try encode(allocator, &(@as([75]u8, @splat(0x42)))); defer allocator.free(max_push); try std.testing.expectEqual(@as(usize, 77), max_push.len); try std.testing.expectEqual(@as(u8, 0x6a), max_push[0]); try std.testing.expectEqual(@as(u8, 75), max_push[1]); - try std.testing.expectEqualSlices(u8, &([_]u8{0x42} ** 75), max_push[2..]); + try std.testing.expectEqualSlices(u8, &(@as([75]u8, @splat(0x42))), max_push[2..]); } test "op_return encode rejects oversized direct pushes" { const allocator = std.testing.allocator; - try std.testing.expectError(error.UnsupportedDataPush, encode(allocator, &([_]u8{0} ** 76))); + try std.testing.expectError(error.UnsupportedDataPush, encode(allocator, &(@as([76]u8, @splat(0))))); } test "op_return matches rejects non-op-return scripts" { diff --git a/src/script/templates/p2pkh.zig b/src/script/templates/p2pkh.zig index d74526c..569f259 100644 --- a/src/script/templates/p2pkh.zig +++ b/src/script/templates/p2pkh.zig @@ -31,7 +31,7 @@ pub fn extractPubKeyHash(locking_script: []const u8) !crypto.Hash160 { } test "p2pkh encode and extract roundtrip" { - const pubkey_hash = crypto.Hash160{ .bytes = [_]u8{0x42} ** 20 }; + const pubkey_hash = crypto.Hash160{ .bytes = @as([20]u8, @splat(0x42)) }; const locking_script = encode(pubkey_hash); try std.testing.expect(matches(&locking_script)); @@ -45,7 +45,7 @@ test "p2pkh match rejects malformed scripts" { test "p2pkh matches the canonical key-one vector across layers" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const expected_pubkey_hash_bytes = try primitives.hex.decode( @@ -73,14 +73,14 @@ test "p2pkh matches the canonical key-one vector across layers" { } test "p2pkh extract rejects near-miss scripts" { - var bad_push = encode(.{ .bytes = [_]u8{0x11} ** 20 }); + var bad_push = encode(.{ .bytes = @as([20]u8, @splat(0x11)) }); bad_push[2] = 0x13; try std.testing.expectError(error.InvalidScriptTemplate, extractPubKeyHash(&bad_push)); - var bad_opcode = encode(.{ .bytes = [_]u8{0x22} ** 20 }); + var bad_opcode = encode(.{ .bytes = @as([20]u8, @splat(0x22)) }); bad_opcode[23] = 0x87; try std.testing.expectError(error.InvalidScriptTemplate, extractPubKeyHash(&bad_opcode)); - var truncated = encode(.{ .bytes = [_]u8{0x33} ** 20 }); + var truncated = encode(.{ .bytes = @as([20]u8, @splat(0x33)) }); try std.testing.expectError(error.InvalidScriptTemplate, extractPubKeyHash(truncated[0 .. truncated.len - 1])); } diff --git a/src/script/templates/pushdrop.zig b/src/script/templates/pushdrop.zig index a94567a..5db6a5f 100644 --- a/src/script/templates/pushdrop.zig +++ b/src/script/templates/pushdrop.zig @@ -121,7 +121,7 @@ pub fn decodeLockBefore(allocator: std.mem.Allocator, script: Script) !?Data { const pk = secp.PublicKey.fromSec1(chunks[0].push_data.data) catch return null; - var fields = std.ArrayListUnmanaged([]const u8){}; + var fields: std.ArrayListUnmanaged([]const u8) = .empty; errdefer freeDecodedFields(allocator, &fields); var i: usize = 2; @@ -159,7 +159,7 @@ pub fn deinitDecoded(allocator: std.mem.Allocator, d: *Data) void { test "pushdrop encode lock-before decode roundtrip" { const a = std.testing.allocator; const crypto = @import("../../crypto/secp256k1.zig"); - const sk = [_]u8{0x01} ++ [_]u8{0} ** 31; + const sk = [_]u8{0x01} ++ @as([31]u8, @splat(0)); const pk = try (try crypto.PrivateKey.fromBytes(sk)).publicKey(); const fields: []const []const u8 = &.{ &[_]u8{3}, &[_]u8{ 2, 1 } }; @@ -176,7 +176,7 @@ test "pushdrop encode lock-before decode roundtrip" { test "pushdrop small-int field roundtrip" { const a = std.testing.allocator; const crypto = @import("../../crypto/secp256k1.zig"); - const sk = [_]u8{0x01} ++ [_]u8{0} ** 31; + const sk = [_]u8{0x01} ++ @as([31]u8, @splat(0)); const pk = try (try crypto.PrivateKey.fromBytes(sk)).publicKey(); const fields: []const []const u8 = &.{&[_]u8{1}}; const script_bytes = try encodeLockBefore(a, &pk.bytes, fields); @@ -189,7 +189,7 @@ test "pushdrop small-int field roundtrip" { test "pushdrop rejects bad drop suffix and trailing junk" { const allocator = std.testing.allocator; const crypto = @import("../../crypto/secp256k1.zig"); - const sk = [_]u8{0x01} ++ [_]u8{0} ** 31; + const sk = [_]u8{0x01} ++ @as([31]u8, @splat(0)); const pk = try (try crypto.PrivateKey.fromBytes(sk)).publicKey(); var wrong = try encodeLockBefore(allocator, &pk.bytes, &[_][]const u8{ "one", "two", "three" }); diff --git a/src/script/templates/r_puzzle.zig b/src/script/templates/r_puzzle.zig index 03e1d15..4c58595 100644 --- a/src/script/templates/r_puzzle.zig +++ b/src/script/templates/r_puzzle.zig @@ -64,7 +64,7 @@ test "r puzzle raw lock matches single-byte vector" { test "r puzzle HASH160 adds hash opcode before push" { const a = std.testing.allocator; - const h = [_]u8{0xab} ** 20; + const h = @as([20]u8, @splat(0xab)); const out = try encodeLock(a, .hash160, &h); defer a.free(out); try std.testing.expect(std.mem.indexOfScalar(u8, out, 0xa9) != null); diff --git a/src/script/thread.zig b/src/script/thread.zig index 98ca6bd..87f84c9 100644 --- a/src/script/thread.zig +++ b/src/script/thread.zig @@ -709,7 +709,7 @@ test "thread verifyScriptsDetailed ignores stale previous locking script context const p2pkh = @import("templates/p2pkh.zig"); const Input = @import("../transaction/input.zig").Input; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try @import("../crypto/lib.zig").PrivateKey.fromBytes(key_bytes); @@ -726,7 +726,7 @@ test "thread verifyScriptsDetailed ignores stale previous locking script context .inputs = &[_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x44} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x44)) }, .index = 0, }, .unlocking_script = Script.init(""), @@ -907,7 +907,7 @@ test "thread verifyPrevoutSpendDetailed uses previous output directly" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = Script.init(&[_]u8{}), @@ -945,7 +945,7 @@ test "thread verifyPrevoutSpendOutcome exposes compact results directly" { .inputs = &[_]@import("../transaction/input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = 0, }, .unlocking_script = Script.init(&[_]u8{}), diff --git a/src/spv/merkle_path.zig b/src/spv/merkle_path.zig index 6ba3013..75a0239 100644 --- a/src/spv/merkle_path.zig +++ b/src/spv/merkle_path.zig @@ -361,8 +361,8 @@ const go_brc74_txid1 = "304e737fdfcb017a1a322e78b067ecebb5e07b44f0a36ed1f01264d2 test "merkle path clone and combine keep owned copies" { const allocator = std.testing.allocator; - const txid_a = crypto.Hash256{ .bytes = [_]u8{0x11} ** 32 }; - const txid_b = crypto.Hash256{ .bytes = [_]u8{0x22} ** 32 }; + const txid_a = crypto.Hash256{ .bytes = @as([32]u8, @splat(0x11)) }; + const txid_b = crypto.Hash256{ .bytes = @as([32]u8, @splat(0x22)) }; var path_a = MerklePath{ .block_height = 10, @@ -384,8 +384,8 @@ test "merkle path clone and combine keep owned copies" { test "merkle path computes missing hashes" { const allocator = std.testing.allocator; - const left = crypto.Hash256{ .bytes = [_]u8{0x33} ** 32 }; - const right = crypto.Hash256{ .bytes = [_]u8{0x44} ** 32 }; + const left = crypto.Hash256{ .bytes = @as([32]u8, @splat(0x33)) }; + const right = crypto.Hash256{ .bytes = @as([32]u8, @splat(0x44)) }; var path = MerklePath{ .block_height = 20, diff --git a/src/spv/verify.zig b/src/spv/verify.zig index 47d1a61..2626e6f 100644 --- a/src/spv/verify.zig +++ b/src/spv/verify.zig @@ -308,7 +308,7 @@ test "verify fee model paid vs required" { errdefer allocator.free(inputs); inputs[0] = .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0xab} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0xab)) }, .index = 0, }, .unlocking_script = .{ .bytes = &[_]u8{0x51} }, diff --git a/src/transaction/beef.zig b/src/transaction/beef.zig index eb729b7..c362d51 100644 --- a/src/transaction/beef.zig +++ b/src/transaction/beef.zig @@ -941,7 +941,7 @@ test "atomic BEEF clones root transaction safely" { @constCast(tx.inputs)[0] = .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 0, }, .unlocking_script = .{ .bytes = &[_]u8{0x51} }, @@ -1300,14 +1300,14 @@ test "BEEF verify checks chain tracker roots" { .expected_height = 7, }, false)); try std.testing.expect(!(try beef.verify(allocator, Tracker{ - .expected_root = .{ .bytes = [_]u8{0xaa} ** 32 }, + .expected_root = .{ .bytes = @as([32]u8, @splat(0xaa)) }, .expected_height = 7, }, false))); } test "BEEF txid-only entries require proof unless explicitly allowed" { const allocator = std.testing.allocator; - const txid = primitives.chainhash.Hash{ .bytes = [_]u8{0x77} ** 32 }; + const txid = primitives.chainhash.Hash{ .bytes = @as([32]u8, @splat(0x77)) }; var beef = newBeefV2(allocator); defer beef.deinit(); diff --git a/src/transaction/builder.zig b/src/transaction/builder.zig index c8c4a34..5b8951f 100644 --- a/src/transaction/builder.zig +++ b/src/transaction/builder.zig @@ -282,7 +282,7 @@ test "builder addInput addOutput builds canonical unsigned transaction" { try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 2, }, .unlocking_script = .empty(), @@ -336,7 +336,7 @@ test "builder sign signs simple p2pkh transaction and built tx survives builder const allocator = std.testing.allocator; var builder = Builder.init(allocator); - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); const public_key = try private_key.publicKey(); @@ -345,7 +345,7 @@ test "builder sign signs simple p2pkh transaction and built tx survives builder try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x22} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x22)) }, .index = 0, }, .unlocking_script = .empty(), @@ -376,13 +376,13 @@ test "builder sign fails when input source output is missing" { var builder = Builder.init(allocator); defer builder.deinit(); - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x33} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x33)) }, .index = 0, }, .unlocking_script = .empty(), @@ -397,7 +397,7 @@ test "builder applyFee fills change output and can signUnsigned afterwards" { var builder = Builder.init(allocator); defer builder.deinit(); - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); const public_key = try private_key.publicKey(); @@ -406,7 +406,7 @@ test "builder applyFee fills change output and can signUnsigned afterwards" { try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x44} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x44)) }, .index = 0, }, .unlocking_script = .empty(), @@ -448,7 +448,7 @@ test "builder applyFee drops zero change output on exact spend" { var builder = Builder.init(allocator); defer builder.deinit(); - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); const public_key = try private_key.publicKey(); @@ -457,7 +457,7 @@ test "builder applyFee drops zero change output on exact spend" { try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x55} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x55)) }, .index = 0, }, .unlocking_script = .empty(), @@ -496,14 +496,14 @@ test "builder signInputP2pkh signs only the requested input" { var builder = Builder.init(allocator); defer builder.deinit(); - var key_a_bytes = [_]u8{0} ** 32; + var key_a_bytes = @as([32]u8, @splat(0)); key_a_bytes[31] = 1; const key_a = try crypto.PrivateKey.fromBytes(key_a_bytes); const pub_a = try key_a.publicKey(); const hash_a = crypto.hash.hash160(&pub_a.bytes); const script_a = script.templates.p2pkh.encode(hash_a); - var key_b_bytes = [_]u8{0} ** 32; + var key_b_bytes = @as([32]u8, @splat(0)); key_b_bytes[31] = 2; const key_b = try crypto.PrivateKey.fromBytes(key_b_bytes); const pub_b = try key_b.publicKey(); @@ -512,7 +512,7 @@ test "builder signInputP2pkh signs only the requested input" { try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x66} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x66)) }, .index = 0, }, .unlocking_script = .empty(), @@ -524,7 +524,7 @@ test "builder signInputP2pkh signs only the requested input" { }); try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x77} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x77)) }, .index = 1, }, .unlocking_script = .empty(), @@ -553,7 +553,7 @@ test "builder finalizeSigned applies fee then signs" { var builder = Builder.init(allocator); defer builder.deinit(); - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); const public_key = try private_key.publicKey(); @@ -562,7 +562,7 @@ test "builder finalizeSigned applies fee then signs" { try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x88} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x88)) }, .index = 0, }, .unlocking_script = .empty(), @@ -600,14 +600,14 @@ test "builder signAllP2pkh signs mixed-key inputs" { var builder = Builder.init(allocator); defer builder.deinit(); - var key_a_bytes = [_]u8{0} ** 32; + var key_a_bytes = @as([32]u8, @splat(0)); key_a_bytes[31] = 1; const key_a = try crypto.PrivateKey.fromBytes(key_a_bytes); const pub_a = try key_a.publicKey(); const hash_a = crypto.hash.hash160(&pub_a.bytes); const script_a = script.templates.p2pkh.encode(hash_a); - var key_b_bytes = [_]u8{0} ** 32; + var key_b_bytes = @as([32]u8, @splat(0)); key_b_bytes[31] = 2; const key_b = try crypto.PrivateKey.fromBytes(key_b_bytes); const pub_b = try key_b.publicKey(); @@ -616,7 +616,7 @@ test "builder signAllP2pkh signs mixed-key inputs" { try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x90} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x90)) }, .index = 0, }, .unlocking_script = .empty(), @@ -628,7 +628,7 @@ test "builder signAllP2pkh signs mixed-key inputs" { }); try builder.addInput(.{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x91} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x91)) }, .index = 1, }, .unlocking_script = .empty(), diff --git a/src/transaction/fees.zig b/src/transaction/fees.zig index 27d4b1f..891856f 100644 --- a/src/transaction/fees.zig +++ b/src/transaction/fees.zig @@ -131,7 +131,7 @@ test "total satoshis and fee compute" { const inputs = try allocator.alloc(@import("input.zig").Input, 1); inputs[0] = .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 0, }, .unlocking_script = .{ .bytes = &[_]u8{0x51} }, @@ -187,7 +187,7 @@ test "fee distributes remainder to first change output" { inputs[0] = .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 0, }, .unlocking_script = .{ .bytes = &[_]u8{0x51} }, diff --git a/src/transaction/output.zig b/src/transaction/output.zig index db9d616..2349878 100644 --- a/src/transaction/output.zig +++ b/src/transaction/output.zig @@ -113,7 +113,7 @@ test "output serialize matches legacy encoding" { .locking_script = .{ .bytes = &[_]u8{ 0x51, 0x51 } }, }; - var buf = [_]u8{0} ** 11; + var buf = @as([11]u8, @splat(0)); const written = output.writeInto(&buf); try std.testing.expectEqual(@as(usize, 11), written); diff --git a/src/transaction/preimage.zig b/src/transaction/preimage.zig index cdf8945..64d320e 100644 --- a/src/transaction/preimage.zig +++ b/src/transaction/preimage.zig @@ -127,15 +127,15 @@ test "preimage parser extracts canonical fields" { [_]u8{ 0x02, 0x00, 0x00, 0x00, } ++ - ([_]u8{0x11} ** 32) ++ - ([_]u8{0x22} ** 32) ++ - ([_]u8{0x33} ** 32) ++ + (@as([32]u8, @splat(0x11))) ++ + (@as([32]u8, @splat(0x22))) ++ + (@as([32]u8, @splat(0x33))) ++ [_]u8{ 0x01, 0x00, 0x00, 0x00 } ++ [_]u8{0x03} ++ [_]u8{ 0x51, 0x76, 0xac } ++ [_]u8{ 0x88, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } ++ [_]u8{ 0xfe, 0xff, 0xff, 0xff } ++ - ([_]u8{0x44} ** 32) ++ + (@as([32]u8, @splat(0x44))) ++ [_]u8{ 0x39, 0x30, 0x00, 0x00 } ++ [_]u8{ 0x41, 0x00, 0x00, 0x00 }; @@ -149,9 +149,9 @@ test "preimage parser extracts canonical fields" { try std.testing.expectEqual(@as(u32, 0xfffffffe), preimage.sequence); try std.testing.expectEqual(@as(u32, 12345), preimage.lockTime()); try std.testing.expectEqual(@as(u32, 65), preimage.sighash_type); - try std.testing.expectEqualSlices(u8, &([_]u8{0x11} ** 32), &preimage.hashPrevouts().bytes); - try std.testing.expectEqualSlices(u8, &([_]u8{0x44} ** 32), &preimage.hashOutputs().bytes); - try std.testing.expectEqualSlices(u8, &([_]u8{0x33} ** 32), outpoint_bytes[0..32]); + try std.testing.expectEqualSlices(u8, &(@as([32]u8, @splat(0x11))), &preimage.hashPrevouts().bytes); + try std.testing.expectEqualSlices(u8, &(@as([32]u8, @splat(0x44))), &preimage.hashOutputs().bytes); + try std.testing.expectEqualSlices(u8, &(@as([32]u8, @splat(0x33))), outpoint_bytes[0..32]); } test "preimage extractor helpers match parsed values" { @@ -159,15 +159,15 @@ test "preimage extractor helpers match parsed values" { [_]u8{ 0x02, 0x00, 0x00, 0x00, } ++ - ([_]u8{0x11} ** 32) ++ - ([_]u8{0x22} ** 32) ++ - ([_]u8{0x33} ** 32) ++ + (@as([32]u8, @splat(0x11))) ++ + (@as([32]u8, @splat(0x22))) ++ + (@as([32]u8, @splat(0x33))) ++ [_]u8{ 0x02, 0x00, 0x00, 0x00 } ++ [_]u8{0x01} ++ [_]u8{0x51} ++ [_]u8{ 0x88, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } ++ [_]u8{ 0xfe, 0xff, 0xff, 0xff } ++ - ([_]u8{0x44} ** 32) ++ + (@as([32]u8, @splat(0x44))) ++ [_]u8{ 0x39, 0x30, 0x00, 0x00 } ++ [_]u8{ 0x41, 0x00, 0x00, 0x00 }; @@ -189,15 +189,15 @@ test "preimage parser rejects truncated and trailing bytes" { [_]u8{ 0x02, 0x00, 0x00, 0x00, } ++ - ([_]u8{0x11} ** 32) ++ - ([_]u8{0x22} ** 32) ++ - ([_]u8{0x33} ** 32) ++ + (@as([32]u8, @splat(0x11))) ++ + (@as([32]u8, @splat(0x22))) ++ + (@as([32]u8, @splat(0x33))) ++ [_]u8{ 0x02, 0x00, 0x00, 0x00 } ++ [_]u8{0x01} ++ [_]u8{0x51} ++ [_]u8{ 0x88, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } ++ [_]u8{ 0xfe, 0xff, 0xff, 0xff } ++ - ([_]u8{0x44} ** 32) ++ + (@as([32]u8, @splat(0x44))) ++ [_]u8{ 0x39, 0x30, 0x00, 0x00 } ++ [_]u8{ 0x41, 0x00, 0x00, 0x00 }; diff --git a/src/transaction/sighash.zig b/src/transaction/sighash.zig index 318f373..e395471 100644 --- a/src/transaction/sighash.zig +++ b/src/transaction/sighash.zig @@ -354,7 +354,7 @@ fn updateLegacyOutput(state: *std.crypto.hash.sha2.Sha256, output: Output) void } fn updateLegacySinglePlaceholderOutput(state: *std.crypto.hash.sha2.Sha256) void { - const satoshis = [_]u8{0xff} ** 8; + const satoshis = @as([8]u8, @splat(0xff)); state.update(&satoshis); state.update(&[_]u8{0x00}); } @@ -366,7 +366,7 @@ fn finalizeDoubleSha256(state: *std.crypto.hash.sha2.Sha256) crypto.Hash256 { } fn legacySingleBugBytes() [32]u8 { - var bytes = [_]u8{0} ** 32; + var bytes = @as([32]u8, @splat(0)); bytes[0] = 0x01; return bytes; } @@ -400,7 +400,7 @@ fn appendLegacyOutput(list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allo } fn appendLegacySinglePlaceholderOutput(list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) !void { - const satoshis = [_]u8{0xff} ** 8; + const satoshis = @as([8]u8, @splat(0xff)); try list.appendSlice(allocator, &satoshis); try list.append(allocator, 0x00); } @@ -470,7 +470,7 @@ test "forkid sighash preimage matches the parser layout" { .inputs = &[_]@import("input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 7, }, .unlocking_script = .{ .bytes = "" }, @@ -506,7 +506,7 @@ test "sighash helper hashes respond to scope flags" { .inputs = &[_]@import("input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x01} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x01)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -514,7 +514,7 @@ test "sighash helper hashes respond to scope flags" { }, .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x02} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x02)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -648,7 +648,7 @@ test "legacy sighash returns the consensus single out-of-range sentinel" { .inputs = &[_]@import("input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x01} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x01)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -656,7 +656,7 @@ test "legacy sighash returns the consensus single out-of-range sentinel" { }, .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x02} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x02)) }, .index = 1, }, .unlocking_script = .{ .bytes = "" }, @@ -689,7 +689,7 @@ test "legacy sighash strips OP_CODESEPARATOR from the subscript" { .inputs = &[_]@import("input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 7, }, .unlocking_script = .{ .bytes = "" }, diff --git a/src/transaction/templates/p2pkh_spend.zig b/src/transaction/templates/p2pkh_spend.zig index 924dd4a..a7ad8ae 100644 --- a/src/transaction/templates/p2pkh_spend.zig +++ b/src/transaction/templates/p2pkh_spend.zig @@ -89,7 +89,7 @@ pub fn verifyInput( test "p2pkh spend signs and verifies a forkid input" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -104,7 +104,7 @@ test "p2pkh spend signs and verifies a forkid input" { .inputs = &[_]@import("../input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x33} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x33)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -133,7 +133,7 @@ test "p2pkh spend signs and verifies a forkid input" { test "p2pkh spend rejects sighash values that do not fit the checksig byte" { const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -148,7 +148,7 @@ test "p2pkh spend rejects sighash values that do not fit the checksig byte" { .inputs = &[_]@import("../input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x33} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x33)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, @@ -173,7 +173,7 @@ test "p2pkh spend rejects sighash values that do not fit the checksig byte" { test "p2pkh interpreter verifies the signed unlocking script end to end" { const interpreter = @import("../../script/interpreter.zig"); const allocator = std.testing.allocator; - var key_bytes = [_]u8{0} ** 32; + var key_bytes = @as([32]u8, @splat(0)); key_bytes[31] = 1; const private_key = try crypto.PrivateKey.fromBytes(key_bytes); @@ -188,7 +188,7 @@ test "p2pkh interpreter verifies the signed unlocking script end to end" { .inputs = &[_]@import("../input.zig").Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x33} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x33)) }, .index = 0, }, .unlocking_script = .{ .bytes = "" }, diff --git a/src/transaction/transaction.zig b/src/transaction/transaction.zig index 430682d..1ca077a 100644 --- a/src/transaction/transaction.zig +++ b/src/transaction/transaction.zig @@ -445,7 +445,7 @@ test "transaction serializes, parses, and hashes canonically" { .inputs = @constCast(&[_]Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x11} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x11)) }, .index = 3, }, .unlocking_script = .{ .bytes = &[_]u8{ 0x51, 0x21, 0x02 } }, diff --git a/src/util.zig b/src/util.zig index fee3dff..61f2453 100644 --- a/src/util.zig +++ b/src/util.zig @@ -44,10 +44,14 @@ test "nowSecs is plausible" { } /// Replacement for `std.testing.refAllDeclsRecursive`, removed in Zig 0.16. +/// Compatible with 0.16 (Declaration structs) and 0.17+ (plain name strings). pub fn refAllDeclsRecursive(comptime T: type) void { if (!builtin.is_test) return; inline for (comptime std.meta.declarations(T)) |decl| { - const D = @field(T, decl.name); + const D = if (comptime @typeInfo(@TypeOf(decl)) == .pointer) + @field(T, decl) + else + @field(T, decl.name); if (comptime @TypeOf(D) == type) { switch (@typeInfo(D)) { .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(D), diff --git a/tests/external_coverage_notice.zig b/tests/external_coverage_notice.zig index e8c8a68..af9484f 100644 --- a/tests/external_coverage_notice.zig +++ b/tests/external_coverage_notice.zig @@ -24,7 +24,7 @@ const external_inputs = [_]ExternalInput{ }, }; -fn envRequiresExternalCoverage(allocator: std.mem.Allocator) bool { +pub fn envRequiresExternalCoverage(allocator: std.mem.Allocator) bool { // Zig 0.16 removed std.process.getEnvVarOwned; without libc the process // environment is only reachable via /proc/self/environ on Linux. if (@import("builtin").os.tag != .linux) return false; @@ -66,11 +66,17 @@ test "external corpus availability is visible in default test runs" { .{ input.name, input.path, step, input.purpose }, ); } else { + if (require_external) { + std.debug.print( + "error: missing required external input '{s}' at {s}; default coverage is incomplete without it ({s})\n", + .{ input.name, input.path, input.purpose }, + ); + return error.MissingExternalCoverageInputs; + } std.debug.print( - "error: missing required external input '{s}' at {s}; default coverage is incomplete without it ({s})\n", + "warning: missing external input '{s}' at {s}; related tests will skip ({s})\n", .{ input.name, input.path, input.purpose }, ); - return error.MissingExternalCoverageInputs; } continue; }, diff --git a/tests/go_corpus_accounting.zig b/tests/go_corpus_accounting.zig index 2f64a74..f01f80b 100644 --- a/tests/go_corpus_accounting.zig +++ b/tests/go_corpus_accounting.zig @@ -11,8 +11,17 @@ fn testIo() std.Io { const corpus_path = "../go-sdk/script/interpreter/data/script_tests.json"; +const coverage_flags = @import("external_coverage_notice.zig"); + fn accessOrRequire(rel_path: []const u8) !void { - try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); + std.Io.Dir.cwd().access(testIo(), rel_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + if (coverage_flags.envRequiresExternalCoverage(std.heap.page_allocator)) return err; + std.debug.print("skipping: external corpus not present: {s}\n", .{rel_path}); + return error.SkipZigTest; + }, + else => return err, + }; } const RowAccounting = struct { diff --git a/tests/go_corpus_filtered_vectors.zig b/tests/go_corpus_filtered_vectors.zig index a9ce873..91d6e12 100644 --- a/tests/go_corpus_filtered_vectors.zig +++ b/tests/go_corpus_filtered_vectors.zig @@ -40,8 +40,17 @@ const SkipReason = enum { unsupported_flags_or_expectation_gap, }; +const coverage_flags = @import("external_coverage_notice.zig"); + fn accessOrRequire(rel_path: []const u8) !void { - try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); + std.Io.Dir.cwd().access(testIo(), rel_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + if (coverage_flags.envRequiresExternalCoverage(std.heap.page_allocator)) return err; + std.debug.print("skipping: external corpus not present: {s}\n", .{rel_path}); + return error.SkipZigTest; + }, + else => return err, + }; } fn containsToken(script_asm: []const u8, needle: []const u8) bool { @@ -335,7 +344,7 @@ fn runDynamicRow(allocator: std.mem.Allocator, qualified: QualifiedRow) !void { var inputs = [_]bsvz.transaction.Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x42} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x42)) }, .index = 0, }, .unlocking_script = Script.init(""), diff --git a/tests/go_exact_corpus_vectors.zig b/tests/go_exact_corpus_vectors.zig index d6561ce..ec45410 100644 --- a/tests/go_exact_corpus_vectors.zig +++ b/tests/go_exact_corpus_vectors.zig @@ -26,8 +26,17 @@ const DynamicRow = struct { expected_text: []const u8, }; +const coverage_flags = @import("external_coverage_notice.zig"); + fn accessOrRequire(rel_path: []const u8) !void { - try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); + std.Io.Dir.cwd().access(testIo(), rel_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + if (coverage_flags.envRequiresExternalCoverage(std.heap.page_allocator)) return err; + std.debug.print("skipping: external corpus not present: {s}\n", .{rel_path}); + return error.SkipZigTest; + }, + else => return err, + }; } fn containsToken(script_asm: []const u8, needle: []const u8) bool { diff --git a/tests/go_meta_rows_vectors.zig b/tests/go_meta_rows_vectors.zig index 489e500..ce9552e 100644 --- a/tests/go_meta_rows_vectors.zig +++ b/tests/go_meta_rows_vectors.zig @@ -16,8 +16,17 @@ const MetaRow = struct { text: []const u8, }; +const coverage_flags = @import("external_coverage_notice.zig"); + fn accessOrRequire(rel_path: []const u8) !void { - try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); + std.Io.Dir.cwd().access(testIo(), rel_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + if (coverage_flags.envRequiresExternalCoverage(std.heap.page_allocator)) return err; + std.debug.print("skipping: external corpus not present: {s}\n", .{rel_path}); + return error.SkipZigTest; + }, + else => return err, + }; } fn expectMetaRows(rows: []const MetaRow) !void { diff --git a/tests/go_multisig_reference_vectors.zig b/tests/go_multisig_reference_vectors.zig index af239db..225d8f9 100644 --- a/tests/go_multisig_reference_vectors.zig +++ b/tests/go_multisig_reference_vectors.zig @@ -33,8 +33,17 @@ const SkipReason = enum { unsupported_flags_or_expectation_gap, }; +const coverage_flags = @import("external_coverage_notice.zig"); + fn accessOrRequire(rel_path: []const u8) !void { - try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); + std.Io.Dir.cwd().access(testIo(), rel_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + if (coverage_flags.envRequiresExternalCoverage(std.heap.page_allocator)) return err; + std.debug.print("skipping: external corpus not present: {s}\n", .{rel_path}); + return error.SkipZigTest; + }, + else => return err, + }; } fn containsToken(script_asm: []const u8, needle: []const u8) bool { diff --git a/tests/go_sigcheck_reference_vectors.zig b/tests/go_sigcheck_reference_vectors.zig index dc48740..7d96663 100644 --- a/tests/go_sigcheck_reference_vectors.zig +++ b/tests/go_sigcheck_reference_vectors.zig @@ -34,8 +34,17 @@ const SkipReason = enum { unsupported_flags_or_expectation_gap, }; +const coverage_flags = @import("external_coverage_notice.zig"); + fn accessOrRequire(rel_path: []const u8) !void { - try std.Io.Dir.cwd().access(testIo(), rel_path, .{}); + std.Io.Dir.cwd().access(testIo(), rel_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + if (coverage_flags.envRequiresExternalCoverage(std.heap.page_allocator)) return err; + std.debug.print("skipping: external corpus not present: {s}\n", .{rel_path}); + return error.SkipZigTest; + }, + else => return err, + }; } fn containsToken(script_asm: []const u8, needle: []const u8) bool { diff --git a/tests/support/go_reference_harness.zig b/tests/support/go_reference_harness.zig index f44935a..88dc075 100644 --- a/tests/support/go_reference_harness.zig +++ b/tests/support/go_reference_harness.zig @@ -35,7 +35,7 @@ pub fn runCase(allocator: std.mem.Allocator, case: Case) !void { var coinbase_inputs = [_]bsvz.transaction.Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0)) }, .index = std.math.maxInt(u32), }, .unlocking_script = Script.init(&.{ 0x00, 0x00 }), diff --git a/tests/support/go_script_harness.zig b/tests/support/go_script_harness.zig index d8a1620..29b773a 100644 --- a/tests/support/go_script_harness.zig +++ b/tests/support/go_script_harness.zig @@ -34,7 +34,7 @@ pub fn runCase(allocator: std.mem.Allocator, case: Case) !void { var inputs = [_]bsvz.transaction.Input{ .{ .previous_outpoint = .{ - .txid = .{ .bytes = [_]u8{0x42} ** 32 }, + .txid = .{ .bytes = @as([32]u8, @splat(0x42)) }, .index = 0, }, .unlocking_script = Script.init(""), diff --git a/tests/template_reference_vectors.zig b/tests/template_reference_vectors.zig index 4934fc7..6e3f61c 100644 --- a/tests/template_reference_vectors.zig +++ b/tests/template_reference_vectors.zig @@ -9,7 +9,7 @@ test "pushdrop encode-decode matches go-sdk field vector shapes" { const pushdrop = bsvz.script.templates.pushdrop; const Script = bsvz.script.Script; - const sk = [_]u8{0x01} ++ [_]u8{0} ** 31; + const sk = [_]u8{0x01} ++ @as([31]u8, @splat(0)); const pk = try (try crypto.PrivateKey.fromBytes(sk)).publicKey(); const pi = [_]u8{ 3, 1, 4, 1, 5, 9 }; @@ -67,8 +67,8 @@ test "r puzzle lock layout matches ts-sdk prefix and per-kind hash opcodes" { const rp = bsvz.script.templates.r_puzzle; const opcode = bsvz.script.opcode.Opcode; - const v20 = [_]u8{0xcd} ** 20; - const v32 = [_]u8{0xef} ** 32; + const v20 = @as([20]u8, @splat(0xcd)); + const v32 = @as([32]u8, @splat(0xef)); const raw = try rp.encodeLock(a, .raw, &[_]u8{0x42}); defer a.free(raw); From 647af55eefb90b0fac961b521b8ea87fe13be37b Mon Sep 17 00:00:00 2001 From: samooth Date: Sun, 23 Aug 2026 21:03:52 +0200 Subject: [PATCH 04/11] Bump minimum_zig_version to 0.16.0 --- build.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig.zon b/build.zig.zon index 3eb5d9c..2cff98a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,7 +1,7 @@ .{ .name = .bsvz, .version = "0.1.0", - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .fingerprint = 0x2f6970cff90bfbc2, .dependencies = .{}, .paths = .{ From d1139a56b82f3aa9916c8170bf90b1ab6094ea4b Mon Sep 17 00:00:00 2001 From: samooth Date: Thu, 27 Aug 2026 03:28:15 +0200 Subject: [PATCH 05/11] docs: add CI badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4bdde3c..db00725 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ![bsvz](assets/banner.png) +![CI](https://github.com/samooth/bsvz/actions/workflows/ci.yml/badge.svg) + # bsvz BSV foundation library for Zig. From e9357db3d11bbfe387c00bc183c1f1421e05c1e5 Mon Sep 17 00:00:00 2001 From: David Case Date: Mon, 23 Mar 2026 03:47:56 -0400 Subject: [PATCH 06/11] Extract ScriptIterator from parseAlloc for streaming script reads ScriptIterator yields one ScriptChunk at a time without allocation. Tracks cursor position and conditional depth internally. Handles OP_RETURN short-circuit for top-level data scripts. parseAlloc, countChunks, isPushOnly, and hasCodeSeparator all refactored to use ScriptIterator internally. --- src/script/parser.zig | 298 +++++++++++++----------------------------- 1 file changed, 92 insertions(+), 206 deletions(-) diff --git a/src/script/parser.zig b/src/script/parser.zig index e445cf4..36db9da 100644 --- a/src/script/parser.zig +++ b/src/script/parser.zig @@ -6,65 +6,95 @@ const Script = @import("script.zig").Script; pub const Error = errors.ScriptError || error{OutOfMemory}; -fn updateConditionalDepth(op: Opcode, depth: *usize) bool { - switch (op) { - .OP_IF, .OP_NOTIF => depth.* += 1, - .OP_ENDIF => { - if (depth.* > 0) depth.* -= 1; - }, - .OP_RETURN => return depth.* == 0, - else => {}, +pub const ScriptIterator = struct { + bytes: []const u8, + cursor: usize = 0, + conditional_depth: usize = 0, + done: bool = false, + + pub fn init(script: Script) ScriptIterator { + return .{ .bytes = script.bytes }; } - return false; -} -fn countChunks(script: Script) Error!usize { - var count: usize = 0; - var cursor: usize = 0; - var conditional_depth: usize = 0; - while (cursor < script.bytes.len) { - const opcode_byte = script.bytes[cursor]; - cursor += 1; + pub fn initBytes(bytes: []const u8) ScriptIterator { + return .{ .bytes = bytes }; + } + + pub fn next(self: *ScriptIterator) errors.ScriptError!?chunk.ScriptChunk { + if (self.done or self.cursor >= self.bytes.len) return null; + + const opcode_byte = self.bytes[self.cursor]; + self.cursor += 1; const op = Opcode.fromByte(opcode_byte); - count += 1; - if (updateConditionalDepth(op, &conditional_depth)) return count; + switch (op) { + .OP_IF, .OP_NOTIF => self.conditional_depth += 1, + .OP_ENDIF => { + if (self.conditional_depth > 0) self.conditional_depth -= 1; + }, + .OP_RETURN => { + if (self.conditional_depth == 0) { + self.done = true; + return .{ .op_return_data = self.bytes[self.cursor..] }; + } + }, + else => {}, + } if (opcode_byte >= 0x01 and opcode_byte <= 0x4b) { - if (script.bytes.len < cursor + opcode_byte) return error.InvalidPushData; - cursor += opcode_byte; - continue; + const len: usize = opcode_byte; + if (self.bytes.len < self.cursor + len) return error.InvalidPushData; + const data = self.bytes[self.cursor .. self.cursor + len]; + self.cursor += len; + return .{ .push_data = .{ .data = data, .encoding = .direct } }; } if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA1)) { - if (cursor >= script.bytes.len) return error.InvalidPushData; - const len = script.bytes[cursor]; - cursor += 1; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; + if (self.cursor >= self.bytes.len) return error.InvalidPushData; + const len = self.bytes[self.cursor]; + self.cursor += 1; + if (self.bytes.len < self.cursor + len) return error.InvalidPushData; + const data = self.bytes[self.cursor .. self.cursor + len]; + self.cursor += len; + return .{ .push_data = .{ .data = data, .encoding = .OP_PUSHDATA1 } }; } if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA2)) { - if (script.bytes.len < cursor + 2) return error.InvalidPushData; - const len = std.mem.readInt(u16, script.bytes[cursor..][0..2], .little); - cursor += 2; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; + if (self.bytes.len < self.cursor + 2) return error.InvalidPushData; + const len = std.mem.readInt(u16, self.bytes[self.cursor..][0..2], .little); + self.cursor += 2; + if (self.bytes.len < self.cursor + len) return error.InvalidPushData; + const data = self.bytes[self.cursor .. self.cursor + len]; + self.cursor += len; + return .{ .push_data = .{ .data = data, .encoding = .OP_PUSHDATA2 } }; } if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA4)) { - if (script.bytes.len < cursor + 4) return error.InvalidPushData; - const len32 = std.mem.readInt(u32, script.bytes[cursor..][0..4], .little); + if (self.bytes.len < self.cursor + 4) return error.InvalidPushData; + const len32 = std.mem.readInt(u32, self.bytes[self.cursor..][0..4], .little); const len = std.math.cast(usize, len32) orelse return error.Overflow; - cursor += 4; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; + self.cursor += 4; + if (self.bytes.len < self.cursor + len) return error.InvalidPushData; + const data = self.bytes[self.cursor .. self.cursor + len]; + self.cursor += len; + return .{ .push_data = .{ .data = data, .encoding = .OP_PUSHDATA4 } }; } + + return .{ .opcode = op }; } + /// Current byte position in the script. + pub fn pos(self: ScriptIterator) usize { + return self.cursor; + } +}; + +fn countChunks(script: Script) Error!usize { + var count: usize = 0; + var iter = ScriptIterator.init(script); + while (try iter.next()) |_| { + count += 1; + } return count; } @@ -77,80 +107,9 @@ pub fn parseAlloc(allocator: std.mem.Allocator, script: Script) Error![]chunk.Sc errdefer chunks.deinit(allocator); try chunks.ensureTotalCapacityPrecise(allocator, try countChunks(script)); - var cursor: usize = 0; - var conditional_depth: usize = 0; - while (cursor < script.bytes.len) { - const opcode_byte = script.bytes[cursor]; - cursor += 1; - const op = Opcode.fromByte(opcode_byte); - - if (updateConditionalDepth(op, &conditional_depth)) { - try chunks.append(allocator, .{ - .op_return_data = script.bytes[cursor..], - }); - return try chunks.toOwnedSlice(allocator); - } - - if (opcode_byte >= 0x01 and opcode_byte <= 0x4b) { - const len: usize = opcode_byte; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - try chunks.append(allocator, .{ - .push_data = .{ - .data = script.bytes[cursor .. cursor + len], - .encoding = .direct, - }, - }); - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA1)) { - if (cursor >= script.bytes.len) return error.InvalidPushData; - const len = script.bytes[cursor]; - cursor += 1; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - try chunks.append(allocator, .{ - .push_data = .{ - .data = script.bytes[cursor .. cursor + len], - .encoding = .OP_PUSHDATA1, - }, - }); - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA2)) { - if (script.bytes.len < cursor + 2) return error.InvalidPushData; - const len = std.mem.readInt(u16, script.bytes[cursor..][0..2], .little); - cursor += 2; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - try chunks.append(allocator, .{ - .push_data = .{ - .data = script.bytes[cursor .. cursor + len], - .encoding = .OP_PUSHDATA2, - }, - }); - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA4)) { - if (script.bytes.len < cursor + 4) return error.InvalidPushData; - const len32 = std.mem.readInt(u32, script.bytes[cursor..][0..4], .little); - const len = std.math.cast(usize, len32) orelse return error.Overflow; - cursor += 4; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - try chunks.append(allocator, .{ - .push_data = .{ - .data = script.bytes[cursor .. cursor + len], - .encoding = .OP_PUSHDATA4, - }, - }); - cursor += len; - continue; - } - - try chunks.append(allocator, .{ .opcode = op }); + var iter = ScriptIterator.init(script); + while (try iter.next()) |c| { + try chunks.append(allocator, c); } return try chunks.toOwnedSlice(allocator); @@ -226,106 +185,33 @@ pub fn serializeAlloc(allocator: std.mem.Allocator, chunks: []const chunk.Script } pub fn isPushOnly(script: Script) Error!bool { - var cursor: usize = 0; - var conditional_depth: usize = 0; - while (cursor < script.bytes.len) { - const opcode_byte = script.bytes[cursor]; - cursor += 1; - const op = Opcode.fromByte(opcode_byte); - - if (op == .OP_RETURN and conditional_depth == 0) return false; - _ = updateConditionalDepth(op, &conditional_depth); - - if (opcode_byte >= 0x01 and opcode_byte <= 0x4b) { - if (script.bytes.len < cursor + opcode_byte) return error.InvalidPushData; - cursor += opcode_byte; - continue; - } - - switch (opcode_byte) { - 0x00, 0x4c, 0x4d, 0x4e, 0x4f, 0x51...0x60 => {}, - else => return false, - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA1)) { - if (cursor >= script.bytes.len) return error.InvalidPushData; - const len = script.bytes[cursor]; - cursor += 1; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA2)) { - if (script.bytes.len < cursor + 2) return error.InvalidPushData; - const len = std.mem.readInt(u16, script.bytes[cursor..][0..2], .little); - cursor += 2; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA4)) { - if (script.bytes.len < cursor + 4) return error.InvalidPushData; - const len32 = std.mem.readInt(u32, script.bytes[cursor..][0..4], .little); - const len = std.math.cast(usize, len32) orelse return error.Overflow; - cursor += 4; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; + var iter = ScriptIterator.init(script); + while (try iter.next()) |c| { + switch (c) { + .push_data => {}, + .op_return_data => return false, + .opcode => |op| { + switch (op) { + .OP_0, .OP_1NEGATE, .OP_RESERVED, + .OP_1, .OP_2, .OP_3, .OP_4, .OP_5, .OP_6, .OP_7, .OP_8, + .OP_9, .OP_10, .OP_11, .OP_12, .OP_13, .OP_14, .OP_15, .OP_16, + => {}, + else => return false, + } + }, } } - return true; } pub fn hasCodeSeparator(script: Script) Error!bool { - var cursor: usize = 0; - var conditional_depth: usize = 0; - while (cursor < script.bytes.len) { - const opcode_byte = script.bytes[cursor]; - cursor += 1; - const op = Opcode.fromByte(opcode_byte); - - if (updateConditionalDepth(op, &conditional_depth)) return false; - - if (opcode_byte >= 0x01 and opcode_byte <= 0x4b) { - if (script.bytes.len < cursor + opcode_byte) return error.InvalidPushData; - cursor += opcode_byte; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA1)) { - if (cursor >= script.bytes.len) return error.InvalidPushData; - const len = script.bytes[cursor]; - cursor += 1; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; + var iter = ScriptIterator.init(script); + while (try iter.next()) |c| { + switch (c) { + .opcode => |op| if (op == .OP_CODESEPARATOR) return true, + else => {}, } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA2)) { - if (script.bytes.len < cursor + 2) return error.InvalidPushData; - const len = std.mem.readInt(u16, script.bytes[cursor..][0..2], .little); - cursor += 2; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_PUSHDATA4)) { - if (script.bytes.len < cursor + 4) return error.InvalidPushData; - const len32 = std.mem.readInt(u32, script.bytes[cursor..][0..4], .little); - const len = std.math.cast(usize, len32) orelse return error.Overflow; - cursor += 4; - if (script.bytes.len < cursor + len) return error.InvalidPushData; - cursor += len; - continue; - } - - if (opcode_byte == @intFromEnum(Opcode.OP_CODESEPARATOR)) return true; } - return false; } From 879430d2578d74d0175d2664499877ea95b68e8a Mon Sep 17 00:00:00 2001 From: David Case Date: Tue, 24 Mar 2026 01:23:25 -0400 Subject: [PATCH 07/11] Remove version field from Beef struct Beef.bytes() always writes V2. V1 serialization is a Transaction concern. Remove bytesV1() from Beef, write BEEF_V2 directly in writeVersionAndBUMPs. --- src/transaction/beef.zig | 59 +++++++--------------------------------- 1 file changed, 10 insertions(+), 49 deletions(-) diff --git a/src/transaction/beef.zig b/src/transaction/beef.zig index c362d51..5a80d31 100644 --- a/src/transaction/beef.zig +++ b/src/transaction/beef.zig @@ -31,7 +31,7 @@ pub const ParsedBeef = struct { if (self.tx) |tx| tx.deinit(self.beef.allocator); self.beef.deinit(); self.* = .{ - .beef = Beef.init(self.beef.allocator, self.beef.version), + .beef = Beef.init(self.beef.allocator), .tx = null, .txid = null, }; @@ -83,14 +83,12 @@ const VerifyResult = struct { pub const Beef = struct { allocator: std.mem.Allocator, - version: u32, bumps: []MerklePath, transactions: std.AutoHashMap(primitives.chainhash.Hash, BeefTx), - pub fn init(allocator: std.mem.Allocator, version: u32) Beef { + pub fn init(allocator: std.mem.Allocator) Beef { return .{ .allocator = allocator, - .version = version, .bumps = &.{}, .transactions = std.AutoHashMap(primitives.chainhash.Hash, BeefTx).init(allocator), }; @@ -104,11 +102,11 @@ pub const Beef = struct { self.transactions.deinit(); for (self.bumps) |*bump| bump.deinit(self.allocator); if (self.bumps.len > 0) self.allocator.free(self.bumps); - self.* = Beef.init(self.allocator, self.version); + self.* = Beef.init(self.allocator); } pub fn clone(self: *const Beef, allocator: std.mem.Allocator) !Beef { - var cloned = Beef.init(allocator, self.version); + var cloned = Beef.init(allocator); errdefer cloned.deinit(); cloned.bumps = try allocator.alloc(MerklePath, self.bumps.len); @@ -141,11 +139,7 @@ pub const Beef = struct { } pub fn bytes(self: *const Beef) ![]u8 { - return switch (self.version) { - BEEF_V1 => self.bytesV1(), - BEEF_V2 => self.bytesV2(), - else => error.InvalidEncoding, - }; + return self.bytesV2(); } pub fn findTransaction(self: *const Beef, txid: primitives.chainhash.Hash) ?*const txmod.Transaction { @@ -399,37 +393,6 @@ pub const Beef = struct { return result; } - fn bytesV1(self: *const Beef) ![]u8 { - var out = std.ArrayList(u8).initCapacity(self.allocator, 128) catch return error.OutOfMemory; - defer out.deinit(self.allocator); - - try writeVersionAndBUMPs(self, &out); - - var buf: [9]u8 = undefined; - const tx_len = try primitives.varint.VarInt.encodeInto(&buf, self.transactions.count()); - try out.appendSlice(self.allocator, buf[0..tx_len]); - - const keys = try collectKeys(self.allocator, &self.transactions); - defer self.allocator.free(keys); - - for (keys) |txid| { - const entry = self.transactions.get(txid) orelse continue; - const tx = entry.transaction orelse continue; - const tx_bytes = try tx.serialize(self.allocator); - defer self.allocator.free(tx_bytes); - try out.appendSlice(self.allocator, tx_bytes); - if (entry.bump_index) |idx| { - try out.append(self.allocator, 1); - const idx_len = try primitives.varint.VarInt.encodeInto(&buf, idx); - try out.appendSlice(self.allocator, buf[0..idx_len]); - } else { - try out.append(self.allocator, 0); - } - } - - return out.toOwnedSlice(self.allocator); - } - fn bytesV2(self: *const Beef) ![]u8 { var out = std.ArrayList(u8).initCapacity(self.allocator, 128) catch return error.OutOfMemory; defer out.deinit(self.allocator); @@ -474,11 +437,11 @@ pub const Beef = struct { }; pub fn newBeefV1(allocator: std.mem.Allocator) Beef { - return Beef.init(allocator, BEEF_V1); + return Beef.init(allocator); } pub fn newBeefV2(allocator: std.mem.Allocator) Beef { - return Beef.init(allocator, BEEF_V2); + return Beef.init(allocator); } pub fn newBeefFromHex(allocator: std.mem.Allocator, hex_text: []const u8) !Beef { @@ -496,7 +459,7 @@ pub fn newBeefFromBytes(allocator: std.mem.Allocator, bytes: []const u8) !Beef { var cursor: usize = 0; const version = try readVersion(bytes, &cursor); - var beef = Beef.init(allocator, version); + var beef = Beef.init(allocator); errdefer beef.deinit(); beef.bumps = try readBUMPs(allocator, bytes, &cursor); @@ -616,7 +579,7 @@ pub fn atomicBeefFromTransaction( var seen = std.AutoHashMap(primitives.chainhash.Hash, void).init(allocator); defer seen.deinit(); - var beef = Beef.init(allocator, BEEF_V2); + var beef = Beef.init(allocator); defer beef.deinit(); const txid = try txidFor(allocator, tx); @@ -687,7 +650,7 @@ pub fn fromBeefInto( fn writeVersionAndBUMPs(self: *const Beef, out: *std.ArrayList(u8)) !void { var ver_buf: [4]u8 = undefined; - std.mem.writeInt(u32, &ver_buf, self.version, .little); + std.mem.writeInt(u32, &ver_buf, BEEF_V2, .little); try out.appendSlice(self.allocator, &ver_buf); var buf: [9]u8 = undefined; @@ -1068,7 +1031,6 @@ test "go-sdk BRC62Hex parses and atomic BEEF round trips" { var beef = try newBeefFromHex(allocator, go_brc62_hex); defer beef.deinit(); - try std.testing.expectEqual(BEEF_V1, beef.version); try std.testing.expectEqual(@as(usize, 1), beef.bumps.len); try std.testing.expectEqual(@as(usize, 2), beef.transactions.count()); @@ -1563,7 +1525,6 @@ test "go-sdk BEEFSet parses known counts and transaction lookup" { var beef = try newBeefFromHex(allocator, go_beef_set); defer beef.deinit(); - try std.testing.expectEqual(BEEF_V2, beef.version); try std.testing.expectEqual(@as(usize, 3), beef.bumps.len); try std.testing.expectEqual(@as(usize, 3), beef.transactions.count()); From 3b1eec2030dace0007370bdcdfe900c4c1336896 Mon Sep 17 00:00:00 2001 From: Satchmo Date: Mon, 30 Mar 2026 10:18:41 -0400 Subject: [PATCH 08/11] Add C ABI export layer for C/C++ integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create src/exports.zig with 18 export fn declarations - BIP39: mnemonic generation + seed derivation - BIP32: HD key from seed, derive path, extract privkey bytes - BRC-42/43: Type-42 key derivation, invoice formatting - Key ops: privkey↔pubkey, pubkey→address, WIF encode/decode - BSM: sign/verify (legacy compat) - BRC-77: sign/verify for anyone and targeted recipients - Create include/bsvz.h C header with extern "C" guards - Add bsvz_c static library target in build.zig - All symbols verified in libbsvz_c.a via nm --- build.zig | 15 +++ include/bsvz.h | 137 +++++++++++++++++++ src/exports.zig | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 493 insertions(+) create mode 100644 include/bsvz.h create mode 100644 src/exports.zig diff --git a/build.zig b/build.zig index f023b94..ecb7bb5 100644 --- a/build.zig +++ b/build.zig @@ -22,6 +22,21 @@ pub fn build(b: *std.Build) void { }); b.installArtifact(lib); + // C-compatible static library with exported symbols + const c_module = b.createModule(.{ + .root_source_file = b.path("src/exports.zig"), + .target = target, + .optimize = optimize, + }); + c_module.addImport("bsvz", root_module); + + const c_lib = b.addLibrary(.{ + .name = "bsvz_c", + .root_module = c_module, + .linkage = .static, + }); + b.installArtifact(c_lib); + const lib_unit_tests = b.addTest(.{ .root_module = root_module, }); diff --git a/include/bsvz.h b/include/bsvz.h new file mode 100644 index 0000000..a84d211 --- /dev/null +++ b/include/bsvz.h @@ -0,0 +1,137 @@ +/* + * bsvz — C ABI for the bsvz Zig BSV library. + * Link against libbsvz_c.a produced by `zig build`. + * + * All functions return 0 on success, negative on failure: + * -1 ERR_INVALID_INPUT + * -2 ERR_CRYPTO + * -3 ERR_BUFFER_TOO_SMALL + * -4 ERR_ALLOC + * -5 ERR_INTERNAL + */ + +#ifndef BSVZ_H +#define BSVZ_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ── BIP39 ─────────────────────────────────────────────────────────── */ + +/* Generate a BIP39 mnemonic. entropy_bits: 128, 160, 192, 224, or 256. + * out_buf must be >= 256 bytes. out_len receives actual length. */ +int bsvz_mnemonic_generate(int entropy_bits, char *out_buf, size_t *out_len); + +/* Derive a 64-byte seed from mnemonic + passphrase (PBKDF2). + * out_seed must be >= 64 bytes. */ +int bsvz_mnemonic_to_seed(const char *mnemonic, size_t mnemonic_len, + const char *passphrase, size_t pass_len, + unsigned char *out_seed); + +/* ── BIP32 ─────────────────────────────────────────────────────────── */ + +/* Create master HD key from seed. out_key receives base58 xprv string. + * out_key must be >= 120 bytes. out_key_len receives actual length. */ +int bsvz_hd_from_seed(const unsigned char *seed, size_t seed_len, + char *out_key, size_t *out_key_len); + +/* Derive child key from base58 xprv/xpub using path like "m/44'/236'/0'/0/0". + * out_key must be >= 120 bytes. out_key_len receives actual length. */ +int bsvz_hd_derive_path(const char *key, size_t key_len, + const char *path, size_t path_len, + char *out_key, size_t *out_key_len); + +/* Extract raw 32-byte private key from base58 xprv. + * out_privkey must be >= 32 bytes. */ +int bsvz_hd_privkey_bytes(const char *key, size_t key_len, + unsigned char *out_privkey); + +/* ── Type-42 / BRC-42 / BRC-43 ────────────────────────────────────── */ + +/* BRC-43: Format invoice number from (security, protocol_id, key_id). + * out_buf must be >= 1300 bytes. out_len receives actual length. */ +int bsvz_brc43_invoice(unsigned char security_level, + const char *protocol, size_t protocol_len, + const char *key_id, size_t key_id_len, + char *out_buf, size_t *out_len); + +/* BRC-42 Type-42: Derive child private key. + * privkey: 32 bytes, counterparty_pubkey: 33 bytes compressed, + * invoice: BRC-43 string. out_privkey: 32 bytes. */ +int bsvz_derive_child_privkey(const unsigned char *privkey, + const unsigned char *counterparty_pubkey, + const char *invoice, size_t invoice_len, + unsigned char *out_privkey); + +/* BRC-42 Type-42: Derive child public key. + * pubkey: 33 bytes compressed, counterparty_privkey: 32 bytes, + * invoice: BRC-43 string. out_pubkey: 33 bytes. */ +int bsvz_derive_child_pubkey(const unsigned char *pubkey, + const unsigned char *counterparty_privkey, + const char *invoice, size_t invoice_len, + unsigned char *out_pubkey); + +/* ── Key operations ────────────────────────────────────────────────── */ + +/* Compressed (33-byte) public key from 32-byte private key. + * out_pubkey must be >= 33 bytes. */ +int bsvz_privkey_to_pubkey(const unsigned char *privkey, + unsigned char *out_pubkey); + +/* P2PKH address (mainnet) from 33-byte compressed pubkey. + * out_addr must be >= 40 bytes. out_addr_len receives actual length. */ +int bsvz_pubkey_to_address(const unsigned char *pubkey, size_t pubkey_len, + char *out_addr, size_t *out_addr_len); + +/* WIF (compressed, mainnet) from 32-byte private key. + * out_wif must be >= 60 bytes. out_wif_len receives actual length. */ +int bsvz_privkey_to_wif(const unsigned char *privkey, + char *out_wif, size_t *out_wif_len); + +/* 32-byte private key from WIF string. + * out_privkey must be >= 32 bytes. */ +int bsvz_wif_to_privkey(const char *wif, size_t wif_len, + unsigned char *out_privkey); + +/* ── BSM (legacy) ──────────────────────────────────────────────────── */ + +/* BSM sign. 65-byte compact sig. out_sig must be >= 65 bytes. */ +int bsvz_bsm_sign(const unsigned char *privkey, + const char *msg, size_t msg_len, + unsigned char *out_sig, size_t *out_sig_len); + +/* BSM verify against P2PKH address. Returns 0 if valid. */ +int bsvz_bsm_verify(const char *address, size_t addr_len, + const char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len); + +/* ── BRC-77 signed messages ────────────────────────────────────────── */ + +/* BRC-77 sign for "anyone". out_sig must be >= 256 bytes. */ +int bsvz_brc77_sign_anyone(const unsigned char *signer_privkey, + const char *msg, size_t msg_len, + unsigned char *out_sig, size_t *out_sig_len); + +/* BRC-77 sign for specific recipient (33-byte compressed pubkey). */ +int bsvz_brc77_sign_for(const unsigned char *signer_privkey, + const unsigned char *recipient_pubkey, + const char *msg, size_t msg_len, + unsigned char *out_sig, size_t *out_sig_len); + +/* BRC-77 verify "anyone" message. Returns 0 if valid. */ +int bsvz_brc77_verify_anyone(const char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len); + +/* BRC-77 verify targeted message. recipient_privkey: 32 bytes. */ +int bsvz_brc77_verify_for(const char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len, + const unsigned char *recipient_privkey); + +#ifdef __cplusplus +} +#endif + +#endif /* BSVZ_H */ diff --git a/src/exports.zig b/src/exports.zig new file mode 100644 index 0000000..a2f6a9c --- /dev/null +++ b/src/exports.zig @@ -0,0 +1,341 @@ +//! C ABI exports for bsvz — allows linking libbsvz.a into C/C++ projects. +//! All functions return 0 on success, negative on failure. +//! Allocations use page_allocator since callers cannot provide a Zig allocator. + +const std = @import("std"); +const bsvz = @import("bsvz"); +const bip39 = bsvz.primitives.bip39; +const bip32 = bsvz.primitives.bip32; +const brc43 = bsvz.primitives.brc43; +const ec = bsvz.primitives.ec; +const crypto = bsvz.crypto; +const compat_wif = bsvz.compat.wif; +const compat_address = bsvz.compat.address; +const compat_bsm = bsvz.compat.bsm; +const msg_signed = bsvz.message.signed; + +const alloc = std.heap.page_allocator; + +// Error codes +const OK: c_int = 0; +const ERR_INVALID_INPUT: c_int = -1; +const ERR_CRYPTO: c_int = -2; +const ERR_BUFFER_TOO_SMALL: c_int = -3; +const ERR_ALLOC: c_int = -4; +const ERR_INTERNAL: c_int = -5; + +fn copyToOut(src: []const u8, out_buf: [*c]u8, out_len: *usize) c_int { + @memcpy(out_buf[0..src.len], src); + out_len.* = src.len; + return OK; +} + +// ── BIP39 ─────────────────────────────────────────────────────────────── + +/// Generate a BIP39 mnemonic. entropy_bits must be 128, 160, 192, 224, or 256. +/// out_buf must be at least 256 bytes. out_len receives actual length. +export fn bsvz_mnemonic_generate(entropy_bits: c_int, out_buf: [*c]u8, out_len: *usize) c_int { + const bits: usize = @intCast(entropy_bits); + const entropy = bip39.newEntropy(alloc, bits) catch return ERR_INVALID_INPUT; + defer alloc.free(entropy); + const mnemonic = bip39.newMnemonic(alloc, entropy) catch return ERR_CRYPTO; + defer alloc.free(mnemonic); + return copyToOut(mnemonic, out_buf, out_len); +} + +/// Derive a 64-byte seed from a mnemonic + passphrase (BIP39 PBKDF2). +/// out_seed must be at least 64 bytes. +export fn bsvz_mnemonic_to_seed( + mnemonic_ptr: [*c]const u8, + mnemonic_len: usize, + passphrase_ptr: [*c]const u8, + pass_len: usize, + out_seed: [*c]u8, +) c_int { + const mnemonic = mnemonic_ptr[0..mnemonic_len]; + const passphrase = passphrase_ptr[0..pass_len]; + const seed = bip39.newSeed(alloc, mnemonic, passphrase) catch return ERR_CRYPTO; + @memcpy(out_seed[0..64], &seed); + return OK; +} + +// ── BIP32 ─────────────────────────────────────────────────────────────── + +/// Create a master HD key from a seed. out_key receives the base58-serialized xprv. +/// out_key must be at least 120 bytes. out_key_len receives actual length. +export fn bsvz_hd_from_seed( + seed_ptr: [*c]const u8, + seed_len: usize, + out_key: [*c]u8, + out_key_len: *usize, +) c_int { + if (seed_len < bip32.min_seed_len or seed_len > bip32.max_seed_len) return ERR_INVALID_INPUT; + const seed = seed_ptr[0..seed_len]; + const master = bip32.newMaster(seed, bip32.Versions.mainnet) catch return ERR_CRYPTO; + const serialized = master.toStringAlloc(alloc) catch return ERR_ALLOC; + defer alloc.free(serialized); + return copyToOut(serialized, out_key, out_key_len); +} + +/// Derive a child key from a base58 xprv/xpub using a BIP32 path like "m/44'/236'/0'/0/0". +/// The "m/" prefix is optional and stripped. out_key receives base58-serialized result. +/// out_key must be at least 120 bytes. +export fn bsvz_hd_derive_path( + key_ptr: [*c]const u8, + key_len: usize, + path_ptr: [*c]const u8, + path_len: usize, + out_key: [*c]u8, + out_key_len: *usize, +) c_int { + const key_str = key_ptr[0..key_len]; + const parent = bip32.parseAlloc(alloc, key_str) catch return ERR_INVALID_INPUT; + var path = path_ptr[0..path_len]; + // Strip "m/" prefix if present + if (path.len >= 2 and path[0] == 'm' and path[1] == '/') { + path = path[2..]; + } else if (path.len == 1 and path[0] == 'm') { + const serialized = parent.toStringAlloc(alloc) catch return ERR_ALLOC; + defer alloc.free(serialized); + return copyToOut(serialized, out_key, out_key_len); + } + const derived = parent.derivePath(path) catch return ERR_CRYPTO; + const serialized = derived.toStringAlloc(alloc) catch return ERR_ALLOC; + defer alloc.free(serialized); + return copyToOut(serialized, out_key, out_key_len); +} + +/// Extract the raw 32-byte private key from an xprv (base58-serialized). +/// out_privkey must be at least 32 bytes. +export fn bsvz_hd_privkey_bytes( + key_ptr: [*c]const u8, + key_len: usize, + out_privkey: [*c]u8, +) c_int { + const key_str = key_ptr[0..key_len]; + const ext = bip32.parseAlloc(alloc, key_str) catch return ERR_INVALID_INPUT; + switch (ext.payload) { + .private => |k| { + @memcpy(out_privkey[0..32], &k); + return OK; + }, + .public => return ERR_INVALID_INPUT, + } +} + +// ── Type-42 / BRC-42 / BRC-43 ────────────────────────────────────────── + +/// BRC-43: Format an invoice number from (security_level, protocol_id, key_id). +/// Returns the invoice string like "2-message signing-abc123". +/// out_buf must be at least 1300 bytes. out_len receives actual length. +export fn bsvz_brc43_invoice( + security_level: u8, + protocol_ptr: [*c]const u8, + protocol_len: usize, + key_id_ptr: [*c]const u8, + key_id_len: usize, + out_buf: [*c]u8, + out_len: *usize, +) c_int { + const protocol = protocol_ptr[0..protocol_len]; + const key_id = key_id_ptr[0..key_id_len]; + const invoice = brc43.formatInvoice(alloc, security_level, protocol, key_id) catch return ERR_INVALID_INPUT; + defer alloc.free(invoice); + return copyToOut(invoice, out_buf, out_len); +} + +/// BRC-42 Type-42 key derivation: derive a child private key. +/// privkey: 32-byte private key, counterparty_pubkey: 33-byte compressed pubkey, +/// invoice: BRC-43 invoice string. out_privkey: 32 bytes. +export fn bsvz_derive_child_privkey( + privkey: [*c]const u8, + counterparty_pubkey: [*c]const u8, + invoice_ptr: [*c]const u8, + invoice_len: usize, + out_privkey: [*c]u8, +) c_int { + const pk = ec.PrivateKey.fromBytes(privkey[0..32].*) catch return ERR_CRYPTO; + const cpub = ec.PublicKey.fromSec1(counterparty_pubkey[0..33]) catch return ERR_CRYPTO; + const invoice = invoice_ptr[0..invoice_len]; + const derived = pk.deriveChild(cpub, invoice) catch return ERR_CRYPTO; + @memcpy(out_privkey[0..32], &derived.toBytes()); + return OK; +} + +/// BRC-42 Type-42 public key derivation: derive a child public key. +/// pubkey: 33-byte compressed pubkey, counterparty_privkey: 32-byte private key, +/// invoice: BRC-43 invoice string. out_pubkey: 33 bytes. +export fn bsvz_derive_child_pubkey( + pubkey: [*c]const u8, + counterparty_privkey: [*c]const u8, + invoice_ptr: [*c]const u8, + invoice_len: usize, + out_pubkey: [*c]u8, +) c_int { + const pub_key = ec.PublicKey.fromSec1(pubkey[0..33]) catch return ERR_CRYPTO; + const cpriv = ec.PrivateKey.fromBytes(counterparty_privkey[0..32].*) catch return ERR_CRYPTO; + const invoice = invoice_ptr[0..invoice_len]; + const derived = pub_key.deriveChild(cpriv, invoice) catch return ERR_CRYPTO; + @memcpy(out_pubkey[0..33], &derived.toCompressedSec1()); + return OK; +} + +// ── Key operations ────────────────────────────────────────────────────── + +/// Get the compressed (33-byte) public key from a 32-byte private key. +/// out_pubkey must be at least 33 bytes. +export fn bsvz_privkey_to_pubkey( + privkey: [*c]const u8, + out_pubkey: [*c]u8, +) c_int { + const pk = crypto.PrivateKey.fromBytes(privkey[0..32].*) catch return ERR_CRYPTO; + const pubk = pk.publicKey() catch return ERR_CRYPTO; + @memcpy(out_pubkey[0..33], &pubk.bytes); + return OK; +} + +/// Encode a compressed public key (33 bytes) as a P2PKH address (mainnet). +/// out_addr must be at least 40 bytes. out_addr_len receives actual length. +export fn bsvz_pubkey_to_address( + pubkey: [*c]const u8, + pubkey_len: usize, + out_addr: [*c]u8, + out_addr_len: *usize, +) c_int { + if (pubkey_len != 33) return ERR_INVALID_INPUT; + const pk = crypto.PublicKey{ .bytes = pubkey[0..33].* }; + const addr = compat_address.encodeP2pkhFromPublicKey(alloc, .mainnet, pk) catch return ERR_CRYPTO; + defer alloc.free(addr); + return copyToOut(addr, out_addr, out_addr_len); +} + +/// Encode a 32-byte private key as WIF (compressed, mainnet). +/// out_wif must be at least 60 bytes. out_wif_len receives actual length. +export fn bsvz_privkey_to_wif( + privkey: [*c]const u8, + out_wif: [*c]u8, + out_wif_len: *usize, +) c_int { + const pk = crypto.PrivateKey.fromBytes(privkey[0..32].*) catch return ERR_CRYPTO; + const wif = compat_wif.encode(alloc, .mainnet, pk, true) catch return ERR_ALLOC; + defer alloc.free(wif); + return copyToOut(wif, out_wif, out_wif_len); +} + +/// Decode a WIF string to a 32-byte private key. +/// out_privkey must be at least 32 bytes. +export fn bsvz_wif_to_privkey( + wif_ptr: [*c]const u8, + wif_len: usize, + out_privkey: [*c]u8, +) c_int { + const wif_str = wif_ptr[0..wif_len]; + const decoded = compat_wif.decode(alloc, wif_str) catch return ERR_INVALID_INPUT; + @memcpy(out_privkey[0..32], &decoded.private_key.toBytes()); + return OK; +} + +// ── BSM (legacy, kept for compat) ─────────────────────────────────────── + +/// Sign a message using Bitcoin Signed Message (BSM) format. +/// Returns a 65-byte compact signature. out_sig must be at least 65 bytes. +export fn bsvz_bsm_sign( + privkey: [*c]const u8, + msg_ptr: [*c]const u8, + msg_len: usize, + out_sig: [*c]u8, + out_sig_len: *usize, +) c_int { + const pk = crypto.PrivateKey.fromBytes(privkey[0..32].*) catch return ERR_CRYPTO; + const message = msg_ptr[0..msg_len]; + const sig = compat_bsm.signMessage(pk, message, alloc) catch return ERR_CRYPTO; + @memcpy(out_sig[0..65], &sig); + out_sig_len.* = 65; + return OK; +} + +/// Verify a BSM signature against a P2PKH address string. +/// Returns 0 if valid, negative if invalid. +export fn bsvz_bsm_verify( + addr_ptr: [*c]const u8, + addr_len: usize, + msg_ptr: [*c]const u8, + msg_len: usize, + sig_ptr: [*c]const u8, + sig_len: usize, +) c_int { + if (sig_len != 65) return ERR_INVALID_INPUT; + const addr_str = addr_ptr[0..addr_len]; + const message = msg_ptr[0..msg_len]; + var sig65: [65]u8 = undefined; + @memcpy(&sig65, sig_ptr[0..65]); + compat_bsm.verifyMessage(alloc, .mainnet, addr_str, sig65, message) catch return ERR_CRYPTO; + return OK; +} + +// ── BRC-77 signed messages ────────────────────────────────────────────── + +/// BRC-77: Sign a message for "anyone" (no specific recipient). +/// out_sig must be at least 256 bytes. out_sig_len receives actual length. +export fn bsvz_brc77_sign_anyone( + signer_privkey: [*c]const u8, + msg_ptr: [*c]const u8, + msg_len: usize, + out_sig: [*c]u8, + out_sig_len: *usize, +) c_int { + const signer = ec.PrivateKey.fromBytes(signer_privkey[0..32].*) catch return ERR_CRYPTO; + const message = msg_ptr[0..msg_len]; + const sig = msg_signed.signAlloc(alloc, message, signer, null) catch return ERR_CRYPTO; + defer alloc.free(sig); + return copyToOut(sig, out_sig, out_sig_len); +} + +/// BRC-77: Sign a message for a specific recipient (33-byte compressed pubkey). +/// out_sig must be at least 256 bytes. out_sig_len receives actual length. +export fn bsvz_brc77_sign_for( + signer_privkey: [*c]const u8, + recipient_pubkey: [*c]const u8, + msg_ptr: [*c]const u8, + msg_len: usize, + out_sig: [*c]u8, + out_sig_len: *usize, +) c_int { + const signer = ec.PrivateKey.fromBytes(signer_privkey[0..32].*) catch return ERR_CRYPTO; + const recipient = ec.PublicKey.fromSec1(recipient_pubkey[0..33]) catch return ERR_CRYPTO; + const message = msg_ptr[0..msg_len]; + const sig = msg_signed.signAlloc(alloc, message, signer, recipient) catch return ERR_CRYPTO; + defer alloc.free(sig); + return copyToOut(sig, out_sig, out_sig_len); +} + +/// BRC-77: Verify an "anyone" signed message. Returns 0 if valid. +export fn bsvz_brc77_verify_anyone( + msg_ptr: [*c]const u8, + msg_len: usize, + sig_ptr: [*c]const u8, + sig_len: usize, +) c_int { + const message = msg_ptr[0..msg_len]; + const sig = sig_ptr[0..sig_len]; + const valid = msg_signed.verify(message, sig, null) catch return ERR_CRYPTO; + if (!valid) return ERR_CRYPTO; + return OK; +} + +/// BRC-77: Verify a targeted signed message. Provide recipient privkey (32 bytes). +/// Returns 0 if valid, negative if invalid. +export fn bsvz_brc77_verify_for( + msg_ptr: [*c]const u8, + msg_len: usize, + sig_ptr: [*c]const u8, + sig_len: usize, + recipient_privkey: [*c]const u8, +) c_int { + const message = msg_ptr[0..msg_len]; + const sig = sig_ptr[0..sig_len]; + const recipient = ec.PrivateKey.fromBytes(recipient_privkey[0..32].*) catch return ERR_CRYPTO; + const valid = msg_signed.verify(message, sig, recipient) catch return ERR_CRYPTO; + if (!valid) return ERR_CRYPTO; + return OK; +} From b852f3df0be50792555a5125b1ba9b3ab29f26a6 Mon Sep 17 00:00:00 2001 From: Satchmo Date: Mon, 30 Mar 2026 19:06:25 -0400 Subject: [PATCH 09/11] Add KeyDeriver for BRC-42/43 protocol key derivation Implements KeyDeriver struct matching Go wallet.KeyDeriver with DerivePublicKey, DerivePrivateKey, and RevealSpecificSecret methods. Supports self, other, and anyone counterparty types. --- src/primitives/key_deriver.zig | 325 +++++++++++++++++++++++++++++++++ src/primitives/lib.zig | 1 + 2 files changed, 326 insertions(+) create mode 100644 src/primitives/key_deriver.zig diff --git a/src/primitives/key_deriver.zig b/src/primitives/key_deriver.zig new file mode 100644 index 0000000..d2a87fe --- /dev/null +++ b/src/primitives/key_deriver.zig @@ -0,0 +1,325 @@ +//! KeyDeriver — BRC-42/43 protocol key derivation. +//! Matches Go `wallet.KeyDeriver` from go-sdk. Uses `ec.PrivateKey.deriveChild` (BRC-42) +//! and `brc43.formatInvoice` to produce protocol-scoped derived keys. +//! https://bsv.brc.dev/key-derivation/0042, https://bsv.brc.dev/key-derivation/0043 + +const std = @import("std"); +const ec = @import("ec.zig"); +const brc43 = @import("brc43.zig"); +const hex = @import("hex.zig"); + +/// Counterparty type for key derivation, matching Go `wallet.CounterpartyType`. +pub const CounterpartyType = enum { + self, + other, + anyone, +}; + +/// Counterparty specification for derivation calls. +pub const Counterparty = struct { + type_: CounterpartyType = .self, + /// Required when `type_ == .other`. Compressed SEC1 public key. + public_key: ?ec.PublicKey = null, +}; + +/// Protocol specification matching Go `wallet.Protocol`. +pub const Protocol = struct { + security_level: u8, + name: []const u8, +}; + +/// The "anyone" private key: a 32-byte key with value 1 (matches Go `AnyoneKey()`). +fn anyonePrivateKey() ec.PrivateKey { + var bytes: [32]u8 = .{0} ** 32; + bytes[31] = 1; + return ec.PrivateKey.fromBytes(bytes) catch unreachable; +} + +/// The "anyone" public key: public key of the anyone private key. +fn anyonePublicKey() ec.PublicKey { + return anyonePrivateKey().publicKey() catch unreachable; +} + +/// KeyDeriver wraps a root private key and provides BRC-42/43 key derivation. +/// Matches Go `wallet.KeyDeriver`. +pub const KeyDeriver = struct { + root_key: ec.PrivateKey, + + /// Create a new KeyDeriver. If `private_key` is null, uses the "anyone" key. + pub fn init(private_key: ?ec.PrivateKey) KeyDeriver { + return .{ + .root_key = private_key orelse anyonePrivateKey(), + }; + } + + /// Returns the root public key (identity key). + pub fn identityKey(self: *const KeyDeriver) !ec.PublicKey { + return self.root_key.publicKey(); + } + + /// Returns identity key as 66-char hex string. + pub fn identityKeyHex(self: *const KeyDeriver, out: *[66]u8) ![]const u8 { + const pub_key = try self.identityKey(); + const compressed = pub_key.toCompressedSec1(); + return hex.encodeLower(&compressed, out); + } + + /// Normalize the counterparty to a concrete PublicKey. + fn normalizeCounterparty(self: *const KeyDeriver, counterparty: Counterparty) !ec.PublicKey { + return switch (counterparty.type_) { + .self => try self.root_key.publicKey(), + .other => counterparty.public_key orelse return error.InvalidEncoding, + .anyone => anyonePublicKey(), + }; + } + + /// Derive a public key using BRC-42/43. Matches Go `KeyDeriver.DerivePublicKey`. + /// + /// When `for_self` is true, derives private key first then extracts public key + /// (used when you own the root key and need the derived public key for yourself). + /// When `for_self` is false, derives the counterparty's child public key + /// (used to derive what the counterparty's derived pubkey would be). + pub fn derivePublicKey( + self: *const KeyDeriver, + allocator: std.mem.Allocator, + protocol: Protocol, + key_id: []const u8, + counterparty: Counterparty, + for_self: bool, + ) !ec.PublicKey { + const counterparty_key = try self.normalizeCounterparty(counterparty); + const invoice = try brc43.formatInvoice(allocator, protocol.security_level, protocol.name, key_id); + defer allocator.free(invoice); + + if (for_self) { + const derived_priv = try self.root_key.deriveChild(counterparty_key, invoice); + return derived_priv.publicKey(); + } + + return counterparty_key.deriveChild(self.root_key, invoice); + } + + /// Derive a private key using BRC-42/43. Matches Go `KeyDeriver.DerivePrivateKey`. + pub fn derivePrivateKey( + self: *const KeyDeriver, + allocator: std.mem.Allocator, + protocol: Protocol, + key_id: []const u8, + counterparty: Counterparty, + ) !ec.PrivateKey { + const counterparty_key = try self.normalizeCounterparty(counterparty); + const invoice = try brc43.formatInvoice(allocator, protocol.security_level, protocol.name, key_id); + defer allocator.free(invoice); + + return self.root_key.deriveChild(counterparty_key, invoice); + } + + /// Reveal the specific key association (HMAC of shared secret + invoice). + /// Matches Go `KeyDeriver.RevealSpecificSecret`. + pub fn revealSpecificSecret( + self: *const KeyDeriver, + allocator: std.mem.Allocator, + counterparty: Counterparty, + protocol: Protocol, + key_id: []const u8, + ) ![32]u8 { + const counterparty_key = try self.normalizeCounterparty(counterparty); + const shared = try self.root_key.deriveSharedSecret(counterparty_key); + const comp = shared.toCompressedSec1(); + + const invoice = try brc43.formatInvoice(allocator, protocol.security_level, protocol.name, key_id); + defer allocator.free(invoice); + + const crypto_hash = @import("../crypto/hash.zig"); + return crypto_hash.hmacSha256(invoice, &comp); + } +}; + +// ── Tests ────────────────────────────────────────────────────────────── + +test "KeyDeriver identity key matches root public key" { + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const identity = try kd.identityKey(); + const root_pub = try root_key.publicKey(); + try std.testing.expect(identity.eql(root_pub)); +} + +test "KeyDeriver identity key hex" { + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + var buf: [66]u8 = undefined; + const hex_str = try kd.identityKeyHex(&buf); + try std.testing.expectEqual(@as(usize, 66), hex_str.len); + try std.testing.expect(hex_str[0] == '0' and (hex_str[1] == '2' or hex_str[1] == '3')); +} + +test "KeyDeriver nil uses anyone key" { + const kd = KeyDeriver.init(null); + const anyone_pub = anyonePublicKey(); + const identity = try kd.identityKey(); + try std.testing.expect(identity.eql(anyone_pub)); +} + +test "KeyDeriver normalizeCounterparty self" { + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const root_pub = try root_key.publicKey(); + const normalized = try kd.normalizeCounterparty(.{ .type_ = .self }); + try std.testing.expect(normalized.eql(root_pub)); +} + +test "KeyDeriver normalizeCounterparty anyone" { + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const anyone_pub = anyonePublicKey(); + const normalized = try kd.normalizeCounterparty(.{ .type_ = .anyone }); + try std.testing.expect(normalized.eql(anyone_pub)); +} + +test "KeyDeriver normalizeCounterparty other" { + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const normalized = try kd.normalizeCounterparty(.{ .type_ = .other, .public_key = cp_pub }); + try std.testing.expect(normalized.eql(cp_pub)); +} + +test "KeyDeriver normalizeCounterparty other without key fails" { + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + try std.testing.expectError(error.InvalidEncoding, kd.normalizeCounterparty(.{ .type_ = .other })); +} + +test "KeyDeriver derivePublicKey for self" { + const a = std.testing.allocator; + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const protocol = Protocol{ .security_level = 0, .name = "testprotocol" }; + const derived = try kd.derivePublicKey(a, protocol, "12345", .{ .type_ = .other, .public_key = cp_pub }, true); + + // Verify this is a valid compressed public key + const comp = derived.toCompressedSec1(); + try std.testing.expect(comp[0] == 0x02 or comp[0] == 0x03); +} + +test "KeyDeriver derivePublicKey for counterparty" { + const a = std.testing.allocator; + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const protocol = Protocol{ .security_level = 0, .name = "testprotocol" }; + const derived = try kd.derivePublicKey(a, protocol, "12345", .{ .type_ = .other, .public_key = cp_pub }, false); + + const comp = derived.toCompressedSec1(); + try std.testing.expect(comp[0] == 0x02 or comp[0] == 0x03); +} + +test "KeyDeriver derivePrivateKey" { + const a = std.testing.allocator; + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const protocol = Protocol{ .security_level = 0, .name = "testprotocol" }; + const derived = try kd.derivePrivateKey(a, protocol, "12345", .{ .type_ = .other, .public_key = cp_pub }); + + // Derived private key should produce a valid public key + const derived_pub = try derived.publicKey(); + const comp = derived_pub.toCompressedSec1(); + try std.testing.expect(comp[0] == 0x02 or comp[0] == 0x03); +} + +test "KeyDeriver forSelf derived pubkey matches private derivation" { + const a = std.testing.allocator; + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const protocol = Protocol{ .security_level = 0, .name = "testprotocol" }; + const counterparty = Counterparty{ .type_ = .other, .public_key = cp_pub }; + + // forSelf: derive private then get pubkey + const for_self_pub = try kd.derivePublicKey(a, protocol, "12345", counterparty, true); + + // Also derive private key directly and check pubkeys match + const derived_priv = try kd.derivePrivateKey(a, protocol, "12345", counterparty); + const priv_pub = try derived_priv.publicKey(); + + try std.testing.expect(for_self_pub.eql(priv_pub)); +} + +test "KeyDeriver anyone derivation works" { + const a = std.testing.allocator; + const kd = KeyDeriver.init(null); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const protocol = Protocol{ .security_level = 0, .name = "testprotocol" }; + const derived = try kd.derivePublicKey(a, protocol, "12345", .{ .type_ = .other, .public_key = cp_pub }, false); + + const comp = derived.toCompressedSec1(); + try std.testing.expect(comp[0] == 0x02 or comp[0] == 0x03); +} + +test "KeyDeriver revealSpecificSecret" { + const a = std.testing.allocator; + const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + const root_key = try ec.PrivateKey.fromBytes(root_bytes); + const kd = KeyDeriver.init(root_key); + + const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); + const cp_pub = try cp_key.publicKey(); + + const protocol = Protocol{ .security_level = 0, .name = "testprotocol" }; + const secret = try kd.revealSpecificSecret(a, .{ .type_ = .other, .public_key = cp_pub }, protocol, "12345"); + + // Should be 32 bytes (HMAC-SHA256 output) + try std.testing.expectEqual(@as(usize, 32), secret.len); + + // Verify manually: HMAC(invoice, sharedSecret.compressed) + const shared = try root_key.deriveSharedSecret(cp_pub); + const comp = shared.toCompressedSec1(); + const crypto_hash = @import("../crypto/hash.zig"); + const invoice = try brc43.formatInvoice(a, 0, "testprotocol", "12345"); + defer a.free(invoice); + const expected = crypto_hash.hmacSha256(invoice, &comp); + try std.testing.expectEqualSlices(u8, &expected, &secret); +} diff --git a/src/primitives/lib.zig b/src/primitives/lib.zig index c0b210b..e92a23f 100644 --- a/src/primitives/lib.zig +++ b/src/primitives/lib.zig @@ -16,3 +16,4 @@ pub const drbg = @import("drbg.zig"); pub const bip39 = @import("bip39.zig"); pub const bip32 = @import("bip32.zig"); pub const brc43 = @import("brc43.zig"); +pub const key_deriver = @import("key_deriver.zig"); From 427ce842455c532f873deeed54beefe3365e508f Mon Sep 17 00:00:00 2001 From: samooth Date: Thu, 27 Aug 2026 17:39:23 +0200 Subject: [PATCH 10/11] fix: Zig 0.17 compat - fix ** array init in key_deriver.zig Replace .{0} ** 32 with @as([32]u8, @splat(0)) for Zig 0.17+ compat --- src/primitives/key_deriver.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primitives/key_deriver.zig b/src/primitives/key_deriver.zig index d2a87fe..4ea1f9d 100644 --- a/src/primitives/key_deriver.zig +++ b/src/primitives/key_deriver.zig @@ -30,7 +30,7 @@ pub const Protocol = struct { /// The "anyone" private key: a 32-byte key with value 1 (matches Go `AnyoneKey()`). fn anyonePrivateKey() ec.PrivateKey { - var bytes: [32]u8 = .{0} ** 32; + var bytes = @as([32]u8, @splat(0)); bytes[31] = 1; return ec.PrivateKey.fromBytes(bytes) catch unreachable; } From 1712e38e0eb1a215f215ca59364340b28c854e05 Mon Sep 17 00:00:00 2001 From: samooth Date: Thu, 27 Aug 2026 17:58:40 +0200 Subject: [PATCH 11/11] fix: Zig 0.17 compat - fix ** array init in key_deriver.zig tests Replace .{0} ** 31 ++ .{val} with @memset for proper 32-byte arrays Also fix variable name in one test --- src/primitives/key_deriver.zig | 72 +++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/src/primitives/key_deriver.zig b/src/primitives/key_deriver.zig index 4ea1f9d..ed2944c 100644 --- a/src/primitives/key_deriver.zig +++ b/src/primitives/key_deriver.zig @@ -138,7 +138,9 @@ pub const KeyDeriver = struct { // ── Tests ────────────────────────────────────────────────────────────── test "KeyDeriver identity key matches root public key" { - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); @@ -148,7 +150,9 @@ test "KeyDeriver identity key matches root public key" { } test "KeyDeriver identity key hex" { - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); @@ -166,7 +170,9 @@ test "KeyDeriver nil uses anyone key" { } test "KeyDeriver normalizeCounterparty self" { - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); @@ -176,7 +182,9 @@ test "KeyDeriver normalizeCounterparty self" { } test "KeyDeriver normalizeCounterparty anyone" { - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); @@ -186,11 +194,15 @@ test "KeyDeriver normalizeCounterparty anyone" { } test "KeyDeriver normalizeCounterparty other" { - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey(); @@ -199,7 +211,9 @@ test "KeyDeriver normalizeCounterparty other" { } test "KeyDeriver normalizeCounterparty other without key fails" { - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); @@ -208,11 +222,15 @@ test "KeyDeriver normalizeCounterparty other without key fails" { test "KeyDeriver derivePublicKey for self" { const a = std.testing.allocator; - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey(); @@ -226,11 +244,15 @@ test "KeyDeriver derivePublicKey for self" { test "KeyDeriver derivePublicKey for counterparty" { const a = std.testing.allocator; - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey(); @@ -243,11 +265,15 @@ test "KeyDeriver derivePublicKey for counterparty" { test "KeyDeriver derivePrivateKey" { const a = std.testing.allocator; - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey(); @@ -262,11 +288,15 @@ test "KeyDeriver derivePrivateKey" { test "KeyDeriver forSelf derived pubkey matches private derivation" { const a = std.testing.allocator; - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey(); @@ -287,7 +317,9 @@ test "KeyDeriver anyone derivation works" { const a = std.testing.allocator; const kd = KeyDeriver.init(null); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey(); @@ -300,11 +332,15 @@ test "KeyDeriver anyone derivation works" { test "KeyDeriver revealSpecificSecret" { const a = std.testing.allocator; - const root_bytes: [32]u8 = .{0} ** 31 ++ .{42}; + var root_bytes: [32]u8 = undefined; + @memset(root_bytes[0..31], 0); + root_bytes[31] = 42; const root_key = try ec.PrivateKey.fromBytes(root_bytes); const kd = KeyDeriver.init(root_key); - const cp_bytes: [32]u8 = .{0} ** 31 ++ .{69}; + var cp_bytes: [32]u8 = undefined; + @memset(cp_bytes[0..31], 0); + cp_bytes[31] = 69; const cp_key = try ec.PrivateKey.fromBytes(cp_bytes); const cp_pub = try cp_key.publicKey();