mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-22 07:16:03 +00:00
Courier vectors: drive the tests from the published JSON
Addresses both asks in the #1472 review. The tests duplicated every vector as a Swift literal, so a format change that updated only the Swift side would have left docs/courier-test-vectors.json silently stale — the document was published as authoritative but nothing checked it. Each test now reads the file and asserts against what it says. The NoiseTestVectors.json precedent does not transfer directly: that fixture lives inside its test target and loads from the test bundle, whereas this one lives at repo-root docs/, outside the package, so SwiftPM cannot carry it as a resource. It resolves the path from #filePath instead, the way bitchatTests/LocalizationCoverageTests already reaches the repo root. CI runs this package with the full tree checked out, and the target is not in the xcodeproj, so there is no bundle-only run to break. Two things beyond a mechanical swap: - The published `label` for the tag HMAC is now load-bearing. The implementation's context string is private, so the test recomputes the tag from the label in the file and compares — otherwise the document could name the wrong label and nothing would notice. - A dedicated test fails loudly if the file is missing or has lost a key, rather than letting a rename skip every assertion while the suite reports green. Also fixes the stale prose. The review caught "the finished 110-byte wire packet"; the same deleted spray-receipt example had left three more wrong numbers in packetSigning._comment — the padding worked example read 46 + 16 = 62 with shortfall 210 = 0xd2, where the real frame is 104 + 16 = 120 with shortfall 152 = 0x98. Proven by mutation, not by green: flipping a byte in envelopeTLV.encoded, a byte in signingPreimage, and the HMAC label each fail the matching test, and removing the file fails all seven. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ae772fdd8
commit
9875c24f6c
@ -86,10 +86,10 @@
|
||||
"",
|
||||
"Padding target is the smallest of [256, 512, 1024, 2048] that fits",
|
||||
"size + 16, applied only when the shortfall is 1...255, every pad byte",
|
||||
"equal to the shortfall. Here: 46 + 16 = 62 <= 256, so target 256,",
|
||||
"shortfall 210 = 0xd2.",
|
||||
"equal to the shortfall. Here: 104 + 16 = 120 <= 256, so target 256,",
|
||||
"shortfall 152 = 0x98.",
|
||||
"",
|
||||
"Signing the 46 unpadded bytes produces a signature that never verifies,",
|
||||
"Signing the 104 unpadded bytes produces a signature that never verifies,",
|
||||
"and nothing in any log points at the cause."
|
||||
],
|
||||
"packet": {
|
||||
@ -111,7 +111,7 @@
|
||||
"Flag bits: hasRecipient 0x01, hasSignature 0x02, isCompressed 0x04,",
|
||||
"hasRoute 0x08, isRSR 0x10. Byte lives at offset 11.",
|
||||
"The unsignedUnpadded and signingPreimage vectors above both carry 0x01",
|
||||
"because the signature is absent from each. Only the finished 110-byte",
|
||||
"because the signature is absent from each. Only the finished 168-byte",
|
||||
"wire packet sets 0x02 as well — which is exactly why re-signing a",
|
||||
"received packet requires clearing that bit before re-encoding."
|
||||
],
|
||||
|
||||
@ -11,10 +11,15 @@ import Foundation
|
||||
import CryptoKit
|
||||
@testable import BitFoundation
|
||||
|
||||
/// Golden vectors for the courier wire format, mirrored in
|
||||
/// Golden vectors for the courier wire format, published as
|
||||
/// `docs/courier-test-vectors.json` so a second implementation can check itself
|
||||
/// without running this app.
|
||||
///
|
||||
/// Every value asserted below is **read from that file**, not duplicated here.
|
||||
/// A format change that updates only the Swift side leaves the published JSON
|
||||
/// stale, and a stale JSON fails these tests — which is the only thing that
|
||||
/// makes the document trustworthy to someone who cannot run it.
|
||||
///
|
||||
/// These cover the three ways a courier client fails *silently* — it builds,
|
||||
/// connects, and delivers nothing, with no error at either end:
|
||||
///
|
||||
@ -29,144 +34,274 @@ import CryptoKit
|
||||
/// agreement instead.
|
||||
struct CourierVectorTests {
|
||||
|
||||
// MARK: Fixed inputs (synthetic — not derived from any real key)
|
||||
// MARK: Loading the published vectors
|
||||
|
||||
static let recipientTag = Data((0..<16).map { UInt8($0) })
|
||||
static let noiseStaticKey = Data((0..<32).map { UInt8(0xA0 &+ $0) })
|
||||
static let ciphertext = Data("courier-vector-ciphertext-0001".utf8)
|
||||
static let expiryMs: UInt64 = 1_800_000_000_000
|
||||
static let epochDay: UInt32 = 20_833
|
||||
static let senderID = Data([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88])
|
||||
static let recipientID = Data([0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00])
|
||||
static let timestampMs: UInt64 = 1_750_000_000_000
|
||||
static let signingSeed = Data(repeating: 0x42, count: 32)
|
||||
/// The published file, located relative to this source file. It lives at
|
||||
/// repo-root `docs/`, outside this package, so it cannot be a SwiftPM
|
||||
/// resource the way `NoiseTestVectors.json` is — the repo already resolves
|
||||
/// a repo-root path this way in `bitchatTests/LocalizationCoverageTests`.
|
||||
static let vectorFileURL = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent() // → BitFoundationTests
|
||||
.deletingLastPathComponent() // → Tests
|
||||
.deletingLastPathComponent() // → BitFoundation
|
||||
.deletingLastPathComponent() // → localPackages
|
||||
.deletingLastPathComponent() // → repo root
|
||||
.appendingPathComponent("docs/courier-test-vectors.json")
|
||||
|
||||
struct MissingVectorFile: Error, CustomStringConvertible {
|
||||
let path: String
|
||||
var description: String {
|
||||
"docs/courier-test-vectors.json not found at \(path). These tests assert "
|
||||
+ "the published vectors; they must not silently pass without them."
|
||||
}
|
||||
}
|
||||
|
||||
static func loadVectors() throws -> Vectors {
|
||||
guard FileManager.default.fileExists(atPath: vectorFileURL.path) else {
|
||||
throw MissingVectorFile(path: vectorFileURL.path)
|
||||
}
|
||||
return try JSONDecoder().decode(Vectors.self,
|
||||
from: try Data(contentsOf: vectorFileURL))
|
||||
}
|
||||
|
||||
/// Decoded shape of the published file. `_comment` keys are prose for human
|
||||
/// readers and are deliberately not decoded; every other key is, so a field
|
||||
/// cannot be renamed or dropped without failing here.
|
||||
struct Vectors: Decodable {
|
||||
struct Inputs: Decodable {
|
||||
let recipientTag: String
|
||||
let noiseStaticKey: String
|
||||
let ciphertextUTF8: String
|
||||
let ciphertext: String
|
||||
let expiryMillis: UInt64
|
||||
let epochDay: UInt32
|
||||
let senderID: String
|
||||
let recipientID: String
|
||||
let timestampMillis: UInt64
|
||||
let signingSeed: String
|
||||
}
|
||||
struct EnvelopeTLV: Decodable {
|
||||
let copies: UInt8
|
||||
let prekeyID: UInt32
|
||||
let encoded: String
|
||||
let encodedLength: Int
|
||||
}
|
||||
struct CopiesClamping: Decodable {
|
||||
struct Case: Decodable {
|
||||
let requested: UInt8
|
||||
let stored: UInt8
|
||||
}
|
||||
let maxCopies: UInt8
|
||||
let cases: [Case]
|
||||
}
|
||||
struct RecipientTagDerivation: Decodable {
|
||||
let label: String
|
||||
let epochDay: UInt32
|
||||
let expected: String
|
||||
}
|
||||
struct CiphertextHash: Decodable {
|
||||
let expected: String
|
||||
}
|
||||
struct PacketSigning: Decodable {
|
||||
struct Packet: Decodable {
|
||||
let version: UInt8
|
||||
let type: String
|
||||
let ttlOnWire: UInt8
|
||||
}
|
||||
struct Flags: Decodable {
|
||||
let unsignedUnpadded: String
|
||||
let signingPreimage: String
|
||||
let signedWirePacket: String
|
||||
}
|
||||
let packet: Packet
|
||||
let unsignedUnpaddedLength: Int
|
||||
let unsignedUnpadded: String
|
||||
let signingPreimageLength: Int
|
||||
let signingPreimagePadByte: String
|
||||
let signingPreimagePadCount: Int
|
||||
let signingPreimage: String
|
||||
let signedWireLength: Int
|
||||
let flags: Flags
|
||||
}
|
||||
struct Signature: Decodable {
|
||||
let publicKey: String
|
||||
let signatureLength: Int
|
||||
let deterministic: Bool
|
||||
}
|
||||
|
||||
let inputs: Inputs
|
||||
let envelopeTLV: EnvelopeTLV
|
||||
let copiesClamping: CopiesClamping
|
||||
let recipientTagDerivation: RecipientTagDerivation
|
||||
let ciphertextHash: CiphertextHash
|
||||
let packetSigning: PacketSigning
|
||||
let signature: Signature
|
||||
}
|
||||
|
||||
/// Fails loudly if the published file is missing, renamed, or has lost a
|
||||
/// key — otherwise a rename would skip every assertion below while the
|
||||
/// suite still reported green.
|
||||
@Test func publishedVectorFileLoads() throws {
|
||||
let v = try Self.loadVectors()
|
||||
#expect(v.inputs.ciphertextUTF8 == "courier-vector-ciphertext-0001")
|
||||
#expect(v.copiesClamping.cases.isEmpty == false)
|
||||
}
|
||||
|
||||
// MARK: Decoded inputs
|
||||
|
||||
private static func hex(_ string: String) throws -> Data {
|
||||
try #require(Data(hexString: string), "not valid hex: \(string)")
|
||||
}
|
||||
|
||||
/// `"0x03"` → `3`.
|
||||
private static func flagByte(_ string: String) throws -> UInt8 {
|
||||
let digits = string.hasPrefix("0x") ? String(string.dropFirst(2)) : string
|
||||
return try #require(UInt8(digits, radix: 16), "not a hex byte: \(string)")
|
||||
}
|
||||
|
||||
private static func envelope(from v: Vectors) throws -> CourierEnvelope {
|
||||
CourierEnvelope(recipientTag: try hex(v.inputs.recipientTag),
|
||||
expiry: v.inputs.expiryMillis,
|
||||
ciphertext: try hex(v.inputs.ciphertext),
|
||||
copies: v.envelopeTLV.copies,
|
||||
prekeyID: v.envelopeTLV.prekeyID)
|
||||
}
|
||||
|
||||
// MARK: Envelope
|
||||
|
||||
/// `expiry` is milliseconds since epoch, big-endian, in an 8-byte TLV.
|
||||
@Test func envelopeTLVEncoding() throws {
|
||||
let envelope = CourierEnvelope(recipientTag: Self.recipientTag,
|
||||
expiry: Self.expiryMs,
|
||||
ciphertext: Self.ciphertext,
|
||||
copies: 4,
|
||||
prekeyID: 0x1122_3344)
|
||||
let encoded = try #require(envelope.encode())
|
||||
#expect(encoded.hexEncodedString() == """
|
||||
010010000102030405060708090a0b0c0d0e0f020008000001a3185c500003001e636f7\
|
||||
5726965722d766563746f722d636970686572746578742d30303031040001040500041122\
|
||||
3344
|
||||
""".replacingOccurrences(of: "\n", with: ""))
|
||||
let v = try Self.loadVectors()
|
||||
|
||||
// 0x02 carries 000001a3185c50 00 == 1_800_000_000_000 ms, not seconds.
|
||||
// The published ciphertext hex and its UTF-8 source must agree, or the
|
||||
// document contradicts itself.
|
||||
#expect(try Self.hex(v.inputs.ciphertext) == Data(v.inputs.ciphertextUTF8.utf8))
|
||||
|
||||
let encoded = try #require(try Self.envelope(from: v).encode())
|
||||
#expect(encoded.hexEncodedString() == v.envelopeTLV.encoded)
|
||||
#expect(encoded.count == v.envelopeTLV.encodedLength)
|
||||
|
||||
// 0x02 carries 000001a3185c5000 == 1_800_000_000_000 ms, not seconds.
|
||||
let decoded = try #require(CourierEnvelope.decode(encoded))
|
||||
#expect(decoded.expiry == Self.expiryMs)
|
||||
#expect(decoded.copies == 4)
|
||||
#expect(decoded.prekeyID == 0x1122_3344)
|
||||
#expect(decoded.expiry == v.inputs.expiryMillis)
|
||||
#expect(decoded.copies == v.envelopeTLV.copies)
|
||||
#expect(decoded.prekeyID == v.envelopeTLV.prekeyID)
|
||||
}
|
||||
|
||||
/// `copies` is clamped into 1...maxCopies, never rejected. An implementation
|
||||
/// that rejects out-of-range values drops envelopes this one accepts.
|
||||
@Test func copiesAreClampedNotRejected() {
|
||||
func copies(_ requested: UInt8) -> UInt8 {
|
||||
CourierEnvelope(recipientTag: Self.recipientTag,
|
||||
expiry: Self.expiryMs,
|
||||
ciphertext: Self.ciphertext,
|
||||
copies: requested).copies
|
||||
@Test func copiesAreClampedNotRejected() throws {
|
||||
let v = try Self.loadVectors()
|
||||
#expect(CourierEnvelope.maxCopies == v.copiesClamping.maxCopies)
|
||||
|
||||
for testCase in v.copiesClamping.cases {
|
||||
let stored = CourierEnvelope(recipientTag: try Self.hex(v.inputs.recipientTag),
|
||||
expiry: v.inputs.expiryMillis,
|
||||
ciphertext: try Self.hex(v.inputs.ciphertext),
|
||||
copies: testCase.requested).copies
|
||||
#expect(stored == testCase.stored,
|
||||
"copies(\(testCase.requested)) expected \(testCase.stored), got \(stored)")
|
||||
}
|
||||
#expect(copies(0) == 1)
|
||||
#expect(copies(200) == CourierEnvelope.maxCopies)
|
||||
#expect(CourierEnvelope.maxCopies == 8)
|
||||
}
|
||||
|
||||
/// HMAC-SHA256(noiseStaticKey, "bitchat-courier-tag-v1" || BE32(epochDay)),
|
||||
/// truncated to 16 bytes.
|
||||
@Test func recipientTagDerivation() {
|
||||
let tag = CourierEnvelope.recipientTag(noiseStaticKey: Self.noiseStaticKey,
|
||||
epochDay: Self.epochDay)
|
||||
#expect(tag.hexEncodedString() == "ad8514c90ca1fa6bf44e38c8a6252482")
|
||||
@Test func recipientTagDerivation() throws {
|
||||
let v = try Self.loadVectors()
|
||||
let noiseStaticKey = try Self.hex(v.inputs.noiseStaticKey)
|
||||
#expect(v.recipientTagDerivation.epochDay == v.inputs.epochDay)
|
||||
|
||||
let tag = CourierEnvelope.recipientTag(noiseStaticKey: noiseStaticKey,
|
||||
epochDay: v.recipientTagDerivation.epochDay)
|
||||
#expect(tag.hexEncodedString() == v.recipientTagDerivation.expected)
|
||||
#expect(tag.count == CourierEnvelope.tagLength)
|
||||
|
||||
// Recompute from the published `label` so that field is load-bearing
|
||||
// too: the implementation's context string is private, so without this
|
||||
// the document could name the wrong label and nothing would notice.
|
||||
var message = Data(v.recipientTagDerivation.label.utf8)
|
||||
withUnsafeBytes(of: v.recipientTagDerivation.epochDay.bigEndian) { message.append(contentsOf: $0) }
|
||||
let recomputed = Data(HMAC<SHA256>.authenticationCode(
|
||||
for: message, using: SymmetricKey(data: noiseStaticKey)
|
||||
).prefix(CourierEnvelope.tagLength))
|
||||
#expect(recomputed == tag)
|
||||
}
|
||||
|
||||
/// The 16-byte envelope identity a spray receipt carries.
|
||||
@Test func ciphertextHashIsSHA256TruncatedTo16() {
|
||||
let hash = Data(Self.ciphertext.sha256Hash().prefix(CourierEnvelope.tagLength))
|
||||
#expect(hash.hexEncodedString() == "bb85dcc4d8b17377c61817992df95826")
|
||||
@Test func ciphertextHashIsSHA256TruncatedTo16() throws {
|
||||
let v = try Self.loadVectors()
|
||||
let hash = Data(try Self.hex(v.inputs.ciphertext)
|
||||
.sha256Hash()
|
||||
.prefix(CourierEnvelope.tagLength))
|
||||
#expect(hash.hexEncodedString() == v.ciphertextHash.expected)
|
||||
}
|
||||
|
||||
// MARK: Packet canonicalization — the trap worth a vector
|
||||
|
||||
/// A real `courierEnvelope` frame: type 0x04 carrying an encoded envelope,
|
||||
/// which is what a deposit actually puts on the wire.
|
||||
static func envelopePacket() throws -> BitchatPacket {
|
||||
let envelope = CourierEnvelope(recipientTag: recipientTag,
|
||||
expiry: expiryMs,
|
||||
ciphertext: ciphertext,
|
||||
copies: 4,
|
||||
prekeyID: 0x1122_3344)
|
||||
return BitchatPacket(type: 0x04,
|
||||
senderID: senderID,
|
||||
recipientID: recipientID,
|
||||
timestamp: timestampMs,
|
||||
payload: try #require(envelope.encode()),
|
||||
signature: nil,
|
||||
ttl: 7)
|
||||
static func envelopePacket(from v: Vectors) throws -> BitchatPacket {
|
||||
BitchatPacket(type: try flagByte(v.packetSigning.packet.type),
|
||||
senderID: try hex(v.inputs.senderID),
|
||||
recipientID: try hex(v.inputs.recipientID),
|
||||
timestamp: v.inputs.timestampMillis,
|
||||
payload: try #require(try envelope(from: v).encode()),
|
||||
signature: nil,
|
||||
ttl: v.packetSigning.packet.ttlOnWire)
|
||||
}
|
||||
|
||||
/// Every byte of the unsigned frame. 14 header + 8 sender + 8 recipient +
|
||||
/// 74 envelope = 104. The envelope is under the 100-byte compression
|
||||
/// threshold, so the compressed path never applies here.
|
||||
static let unpaddedHex = """
|
||||
010407000001977420dc0001004a112233445566778899aabbccddeeff000100100001020\
|
||||
30405060708090a0b0c0d0e0f020008000001a3185c500003001e636f75726965722d76656\
|
||||
3746f722d636970686572746578742d303030310400010405000411223344
|
||||
""".replacingOccurrences(of: "\n", with: "")
|
||||
|
||||
/// Identical to `unpaddedHex` except `ttl` at offset 2 is zeroed.
|
||||
static let preimageBodyHex = """
|
||||
010400000001977420dc0001004a112233445566778899aabbccddeeff000100100001020\
|
||||
30405060708090a0b0c0d0e0f020008000001a3185c500003001e636f75726965722d76656\
|
||||
3746f722d636970686572746578742d303030310400010405000411223344
|
||||
""".replacingOccurrences(of: "\n", with: "")
|
||||
|
||||
/// The signed pre-image is **not** the wire bytes. It zeroes `ttl`, clears
|
||||
/// the `hasSignature` flag, and is PKCS#7-padded to a block boundary.
|
||||
/// Both sequences are compared in full — a change to timestamp encoding,
|
||||
/// payload length, or field layout has to fail here, otherwise the JSON
|
||||
/// fixture could go stale while this stayed green.
|
||||
@Test func signingPreimageIsTTLZeroedAndPadded() throws {
|
||||
let packet = try Self.envelopePacket()
|
||||
let v = try Self.loadVectors()
|
||||
let signing = v.packetSigning
|
||||
let packet = try Self.envelopePacket(from: v)
|
||||
|
||||
let unpadded = try #require(BinaryProtocol.encode(packet, padding: false))
|
||||
#expect(unpadded.count == 104)
|
||||
#expect(unpadded.hexEncodedString() == Self.unpaddedHex)
|
||||
#expect(unpadded.count == signing.unsignedUnpaddedLength)
|
||||
#expect(unpadded.hexEncodedString() == signing.unsignedUnpadded)
|
||||
|
||||
let preimage = try #require(packet.toBinaryDataForSigning())
|
||||
#expect(preimage.count == 256)
|
||||
#expect(preimage.hexEncodedString()
|
||||
== Self.preimageBodyHex + String(repeating: "98", count: 152))
|
||||
#expect(preimage.count == signing.signingPreimageLength)
|
||||
#expect(preimage.hexEncodedString() == signing.signingPreimage)
|
||||
|
||||
// 152 == 0x98 == the shortfall to the 256-byte block, and every pad
|
||||
// byte equals it.
|
||||
#expect(preimage.dropFirst(104).allSatisfy { $0 == 152 })
|
||||
#expect(preimage[BinaryProtocol.Offsets.flags] == BinaryProtocol.Flags.hasRecipient)
|
||||
#expect(unpadded[2] == 7 && preimage[2] == 0) // ttl on wire vs signed
|
||||
// The pad byte equals the shortfall to the block boundary, and every
|
||||
// pad byte equals it.
|
||||
let padByte = try Self.flagByte(signing.signingPreimagePadByte)
|
||||
#expect(Int(padByte) == signing.signingPreimagePadCount)
|
||||
#expect(signing.unsignedUnpaddedLength + signing.signingPreimagePadCount
|
||||
== signing.signingPreimageLength)
|
||||
#expect(preimage.dropFirst(signing.unsignedUnpaddedLength).count
|
||||
== signing.signingPreimagePadCount)
|
||||
#expect(preimage.dropFirst(signing.unsignedUnpaddedLength).allSatisfy { $0 == padByte })
|
||||
|
||||
#expect(preimage[BinaryProtocol.Offsets.flags]
|
||||
== (try Self.flagByte(signing.flags.signingPreimage)))
|
||||
#expect(unpadded[BinaryProtocol.Offsets.flags]
|
||||
== (try Self.flagByte(signing.flags.unsignedUnpadded)))
|
||||
#expect(unpadded[2] == signing.packet.ttlOnWire && preimage[2] == 0)
|
||||
#expect(unpadded[0] == signing.packet.version)
|
||||
}
|
||||
|
||||
/// Ed25519 — CryptoKit's `Curve25519.Signing`, not X25519 key agreement.
|
||||
@Test func signatureOverPreimageVerifies() throws {
|
||||
let packet = try Self.envelopePacket()
|
||||
let v = try Self.loadVectors()
|
||||
let packet = try Self.envelopePacket(from: v)
|
||||
let preimage = try #require(packet.toBinaryDataForSigning())
|
||||
|
||||
// Pin what is being signed. Without this the test is self-consistent by
|
||||
// construction — it would sign whatever it was handed, verify it, and
|
||||
// pass over a wrong canonicalization. Proven by mutating an input and
|
||||
// watching this line, not the verification below, be the one that fails.
|
||||
#expect(preimage.hexEncodedString()
|
||||
== Self.preimageBodyHex + String(repeating: "98", count: 152))
|
||||
#expect(preimage.hexEncodedString() == v.packetSigning.signingPreimage)
|
||||
|
||||
let key = try Curve25519.Signing.PrivateKey(rawRepresentation: Self.signingSeed)
|
||||
#expect(key.publicKey.rawRepresentation.hexEncodedString()
|
||||
== "2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12")
|
||||
let key = try Curve25519.Signing.PrivateKey(
|
||||
rawRepresentation: try Self.hex(v.inputs.signingSeed)
|
||||
)
|
||||
#expect(key.publicKey.rawRepresentation.hexEncodedString() == v.signature.publicKey)
|
||||
|
||||
// Signature BYTES are deliberately not pinned. CryptoKit's Ed25519
|
||||
// signing is randomized rather than the deterministic RFC 8032
|
||||
@ -176,20 +311,21 @@ struct CourierVectorTests {
|
||||
// What must match is the pre-image above; verification is the contract.
|
||||
let a = try key.signature(for: preimage)
|
||||
let b = try key.signature(for: preimage)
|
||||
#expect(v.signature.deterministic == false)
|
||||
#expect(Data(a) != Data(b), "CryptoKit Ed25519 signing is randomized")
|
||||
#expect(key.publicKey.isValidSignature(a, for: preimage))
|
||||
#expect(key.publicKey.isValidSignature(b, for: preimage))
|
||||
#expect(Data(a).count == BinaryProtocol.signatureSize)
|
||||
#expect(Data(a).count == v.signature.signatureLength)
|
||||
|
||||
let signature = a
|
||||
|
||||
// On the wire the packet is 168 bytes — the 104 above plus a 64-byte
|
||||
// signature — and the flags byte now also carries hasSignature.
|
||||
// On the wire the packet carries the 64-byte signature on top of the
|
||||
// unsigned frame, and the flags byte now also carries hasSignature.
|
||||
var signed = packet
|
||||
signed.signature = Data(signature)
|
||||
signed.signature = Data(a)
|
||||
let wire = try #require(BinaryProtocol.encode(signed, padding: false))
|
||||
#expect(wire.count == 168)
|
||||
#expect(wire.count == v.packetSigning.signedWireLength)
|
||||
#expect(wire.count == v.packetSigning.unsignedUnpaddedLength + v.signature.signatureLength)
|
||||
#expect(wire[BinaryProtocol.Offsets.flags]
|
||||
== BinaryProtocol.Flags.hasRecipient | BinaryProtocol.Flags.hasSignature)
|
||||
== (try Self.flagByte(v.packetSigning.flags.signedWirePacket)))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user