mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-08 06:56:10 +00:00
Courier vectors: use a real deposit frame and pin every byte
Both changes address the Codex review on #1472. The signing vector used type 0x2a, which is not a MessageType on main — it is the spray-receipt opcode from a separate unmerged branch, so the frame decoded to nothing and was misleading as an end-to-end courier vector. It now encodes an actual courierEnvelope deposit: type 0x04 carrying the encoded envelope from the TLV vector as its payload. 14 header + 8 sender + 8 recipient + 74 envelope = 104 unsigned and unpadded, 256 signed, 168 on the wire. The envelope is under the 100-byte compression threshold, so the compressed path stays out of it. The pre-image assertions checked only length, the first three bytes, the flags byte and the padding suffix. A change to timestamp encoding, payload length or field layout could have left the test green while the JSON went stale — which made the claim that these cannot drift without a test failing untrue as written. Both the unpadded and padded sequences are now compared in full. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0a5647b3e8
commit
6a675ab4fc
@ -93,18 +93,19 @@
|
||||
"and nothing in any log points at the cause."
|
||||
],
|
||||
"packet": {
|
||||
"_comment": "A real courierEnvelope deposit frame: type 0x04 carrying the encoded envelope from envelopeTLV.encoded above as its payload.",
|
||||
"version": 1,
|
||||
"type": "0x2a",
|
||||
"type": "0x04",
|
||||
"ttlOnWire": 7,
|
||||
"payload": "bb85dcc4d8b17377c61817992df95826"
|
||||
"payloadIs": "envelopeTLV.encoded (74 bytes)"
|
||||
},
|
||||
"unsignedUnpaddedLength": 46,
|
||||
"unsignedUnpadded": "012a07000001977420dc00010010112233445566778899aabbccddeeff00bb85dcc4d8b17377c61817992df95826",
|
||||
"unsignedUnpaddedLength": 104,
|
||||
"unsignedUnpadded": "010407000001977420dc0001004a112233445566778899aabbccddeeff00010010000102030405060708090a0b0c0d0e0f020008000001a3185c500003001e636f75726965722d766563746f722d636970686572746578742d303030310400010405000411223344",
|
||||
"signingPreimageLength": 256,
|
||||
"signingPreimagePadByte": "0xd2",
|
||||
"signingPreimagePadCount": 210,
|
||||
"signingPreimage": "012a00000001977420dc00010010112233445566778899aabbccddeeff00bb85dcc4d8b17377c61817992df95826d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2",
|
||||
"signedWireLength": 110,
|
||||
"signingPreimagePadByte": "0x98",
|
||||
"signingPreimagePadCount": 152,
|
||||
"signingPreimage": "010400000001977420dc0001004a112233445566778899aabbccddeeff00010010000102030405060708090a0b0c0d0e0f020008000001a3185c500003001e636f75726965722d766563746f722d636970686572746578742d3030303104000104050004112233449898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898989898",
|
||||
"signedWireLength": 168,
|
||||
"flags": {
|
||||
"_comment": [
|
||||
"Flag bits: hasRecipient 0x01, hasSignature 0x02, isCompressed 0x04,",
|
||||
|
||||
@ -95,40 +95,66 @@ struct CourierVectorTests {
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
/// 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 payload = Data(Self.ciphertext.sha256Hash().prefix(CourierEnvelope.tagLength))
|
||||
let packet = BitchatPacket(type: 0x2A,
|
||||
senderID: Self.senderID,
|
||||
recipientID: Self.recipientID,
|
||||
timestamp: Self.timestampMs,
|
||||
payload: payload,
|
||||
signature: nil,
|
||||
ttl: 7)
|
||||
let packet = try Self.envelopePacket()
|
||||
|
||||
// Unsigned, unpadded: 14 header + 8 sender + 8 recipient + 16 payload.
|
||||
let unpadded = try #require(BinaryProtocol.encode(packet, padding: false))
|
||||
#expect(unpadded.count == 46)
|
||||
#expect(unpadded.hexEncodedString().hasPrefix("012a07")) // ttl = 7 here
|
||||
#expect(unpadded.count == 104)
|
||||
#expect(unpadded.hexEncodedString() == Self.unpaddedHex)
|
||||
|
||||
let preimage = try #require(packet.toBinaryDataForSigning())
|
||||
#expect(preimage.count == 256) // padded, not 46
|
||||
#expect(preimage.hexEncodedString().hasPrefix("012a00")) // ttl zeroed
|
||||
#expect(preimage.count == 256)
|
||||
#expect(preimage.hexEncodedString()
|
||||
== Self.preimageBodyHex + String(repeating: "98", count: 152))
|
||||
|
||||
// 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(preimage.dropFirst(46).allSatisfy { $0 == 210 }) // 210 == 0xD2 == pad length
|
||||
#expect(unpadded[2] == 7 && preimage[2] == 0) // ttl on wire vs signed
|
||||
}
|
||||
|
||||
/// Ed25519 — CryptoKit's `Curve25519.Signing`, not X25519 key agreement.
|
||||
@Test func signatureOverPreimageVerifies() throws {
|
||||
let payload = Data(Self.ciphertext.sha256Hash().prefix(CourierEnvelope.tagLength))
|
||||
let packet = BitchatPacket(type: 0x2A,
|
||||
senderID: Self.senderID,
|
||||
recipientID: Self.recipientID,
|
||||
timestamp: Self.timestampMs,
|
||||
payload: payload,
|
||||
signature: nil,
|
||||
ttl: 7)
|
||||
let packet = try Self.envelopePacket()
|
||||
let preimage = try #require(packet.toBinaryDataForSigning())
|
||||
|
||||
let key = try Curve25519.Signing.PrivateKey(rawRepresentation: Self.signingSeed)
|
||||
@ -150,12 +176,12 @@ struct CourierVectorTests {
|
||||
|
||||
let signature = a
|
||||
|
||||
// On the wire the packet is 110 bytes — the 46 above plus a 64-byte
|
||||
// On the wire the packet is 168 bytes — the 104 above plus a 64-byte
|
||||
// signature — and the flags byte now also carries hasSignature.
|
||||
var signed = packet
|
||||
signed.signature = Data(signature)
|
||||
let wire = try #require(BinaryProtocol.encode(signed, padding: false))
|
||||
#expect(wire.count == 110)
|
||||
#expect(wire.count == 168)
|
||||
#expect(wire[BinaryProtocol.Offsets.flags]
|
||||
== BinaryProtocol.Flags.hasRecipient | BinaryProtocol.Flags.hasSignature)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user