Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Docs/plans/2026-07-25-chacha-poly1305.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Package B — ChaCha20 / XChaCha20 / Poly1305 AEAD

**Branch:** `package/chacha-poly1305`
**Base:** package A (`package/aead-architecture` @ ~47c7463)
**Donor:** git ref `pr-90` (d6eb393) — algorithm only

## Goal

Ship ChaCha20, XChaCha20, and Poly1305 AEAD without re-applying PR #90’s AEAD base /
GCM rewrite. Package A architecture stays authoritative.

## Decisions

| Topic | Decision |
|-------|----------|
| Base AEAD API | Keep package A: virtual `Done`, abstract `Encode`/`Decode` on `TAuthenticatedCipherModesBase`. No `InitAuth` / `UpdateWithEncDecBuf` / `FinalizeMAC` template. |
| Single auth object | `FAuthObj` only; `cmPoly1305` → `TPoly1305.Create` |
| Lifecycle | Init → AAD/tag props → Encode*/Decode* → Done → tag |
| Protected names | `EncodeGCM` / `DecodeGCM` dispatch retained for all auth modes |
| Poly1305 kernel default | **`pmPas`** (pure Pascal). AVX kept behind `CpuMode` / `{$IFNDEF PUREPASCAL}` but not auto-selected. |
| ChaCha kernel default | **`cmPas`**. SSE/AVX optional via `TCipher_ChaCha20.CpuMode`; no auto-upgrade on init. |
| AES-NI / UseAESAsm | **Not ported** from PR #90. |
| ChaCha default Mode | **Not** `cmPoly1305`. AEAD is opt-in (`Mode := cmPoly1305`). |
| Poly key for AEAD | ChaCha `OnAfterInitVectorInitialization`: counter 0 → 32-byte one-time key → `FAuthObj.Init`; payload continues at counter 1. |
| AVX FinalizePoly1305 bug | Fixed limb masking (`g[1]/`/`g[2]`/`g[3]` not repeated `g[0]`). |
| Empty PT / AAD-only | `DECCipherFormats` empty path uses `IsAuthenticatedBlockMode` (GCM, CCM, Poly1305). |
| Speed unit tests | Omitted (CI-flaky / non-functional). |

## Files (summary)

- New: `Source/DECCPUSupport.pas`, `Source/DECCipherModesPoly1305.pas`
- Port: `TCipher_ChaCha20`, `TCipher_XChaCha20` in `Source/DECCiphers.pas`
- Wire: `DECCipherBase`, `DECCipherModes`, `DECCipherFormats`, `DECUtil` (`AlignPtr32`)
- Tests: `Unit Tests/Tests/TestDECChaChaPoly1305.pas`, `Unit Tests/Data/chacha20_poly1305_test.json`

## Out of scope

- GCM/CCM algorithm changes
- PR #90 dual-object auth redesign
- AES-NI
- Default-enable AVX Poly1305
6 changes: 4 additions & 2 deletions Source/DEC60.dpr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{*****************************************************************************
{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
Expand Down Expand Up @@ -49,7 +49,9 @@ uses
DECZIPHelper in 'DECZIPHelper.pas',
DECCipherPaddings in 'DECCipherPaddings.pas',
DECCipherModesCCM in 'DECCipherModesCCM.pas',
DECAuthenticatedCipherModesBase in 'DECAuthenticatedCipherModesBase.pas';
DECAuthenticatedCipherModesBase in 'DECAuthenticatedCipherModesBase.pas',
DECCipherModesPoly1305 in 'DECCipherModesPoly1305.pas',
DECCPUSupport in 'DECCPUSupport.pas';

begin
try
Expand Down
2 changes: 2 additions & 0 deletions Source/DEC60.dproj
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@
<DCCReference Include="DECCipherPaddings.pas"/>
<DCCReference Include="DECCipherModesCCM.pas"/>
<DCCReference Include="DECAuthenticatedCipherModesBase.pas"/>
<DCCReference Include="DECCipherModesPoly1305.pas"/>
<DCCReference Include="DECCPUSupport.pas"/>
<None Include="DECHash.asm86.inc"/>
<None Include="DECOptions.inc"/>
<None Include="BuildAll.cmd"/>
Expand Down
181 changes: 181 additions & 0 deletions Source/DECCPUSupport.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. A copy of this licence is found in the root directory
of this project in the file LICENCE.txt or alternatively at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*****************************************************************************}

/// <summary>
/// x86 / x64 CPU feature detection (CPUID).
/// On non-x86 platforms all feature flags remain False.
/// </summary>
unit DECCPUSupport;

interface

type
/// <summary>
/// Result of a CPUID leaf query (EAX/EBX/ECX/EDX register values)
/// </summary>
TCPUIDRec = record
EAX: Cardinal;
EBX: Cardinal;
ECX: Cardinal;
EDX: Cardinal;
end;

/// <summary>
/// Static CPU feature flags, filled once at unit initialization.
/// </summary>
TDEC_CPUSupport = class(TObject)
public
class var AES: Boolean;
class var AVX: Boolean;
class var AVX2: Boolean;

class var SSE: Boolean;
class var SSE2: Boolean;
class var SSE3: Boolean;
class var SSE41: Boolean;
class var SSE42: Boolean;

class var RDRand: Boolean;
class var RDSeed: Boolean;
end;

/// <summary>
/// Execute CPUID with the given leaf / sub-leaf. Non-x86 stubs return zeros.
/// </summary>
function GetCPUID(ALeaf: Cardinal; ASubLeaf: Cardinal = 0): TCPUIDRec;

implementation

{$IFDEF CPUX64}
{$DEFINE x64}
{$ENDIF}
{$IFDEF cpux86_64}
{$DEFINE x64}
{$ENDIF}

{$IFDEF CPU86}
{$DEFINE x86}
{$ENDIF}
{$IFDEF CPUX86}
{$DEFINE x86}
{$ENDIF}
{$IFDEF CPU386}
{$DEFINE x86}
{$ENDIF}

{$IF Defined(x64) or Defined(x86)}

/// <summary>
/// Low-level CPUID. Uses a procedure so calling conventions stay simple:
/// x86 register: EAX=Leaf, EDX=SubLeaf, ECX=@Out
/// x64 Win: RCX=Leaf, RDX=SubLeaf, R8=@Out
/// </summary>
procedure CPUIDQuery(ALeaf, ASubLeaf: Cardinal; var AOut: TCPUIDRec);
{$IFDEF x64}
asm
// RCX = ALeaf, RDX = ASubLeaf, R8 = @AOut
push rbx
mov r10, r8
mov eax, ecx
mov ecx, edx
cpuid
mov dword ptr [r10 + 0], eax
mov dword ptr [r10 + 4], ebx
mov dword ptr [r10 + 8], ecx
mov dword ptr [r10 + 12], edx
pop rbx
end;
{$ELSE}
asm
// EAX = ALeaf, EDX = ASubLeaf, ECX = @AOut
push ebx
push edi
mov edi, ecx
mov ecx, edx
// EAX already holds ALeaf
cpuid
mov [edi + 0], eax
mov [edi + 4], ebx
mov [edi + 8], ecx
mov [edi + 12], edx
pop edi
pop ebx
end;
{$ENDIF}

function GetCPUID(ALeaf: Cardinal; ASubLeaf: Cardinal = 0): TCPUIDRec;
begin
CPUIDQuery(ALeaf, ASubLeaf, Result);
end;

procedure InitFlags;
var
Reg: TCPUIDRec;
NIds: Cardinal;
begin
Reg := GetCPUID(0, 0);
NIds := Reg.EAX;

if NIds >= 1 then
begin
Reg := GetCPUID(1, 0);
TDEC_CPUSupport.SSE := (Reg.EDX and (1 shl 25)) <> 0;
TDEC_CPUSupport.SSE2 := (Reg.EDX and (1 shl 26)) <> 0;
// SSE3 is ECX bit 0 (bit 9 is SSSE3)
TDEC_CPUSupport.SSE3 := (Reg.ECX and (1 shl 0)) <> 0;
TDEC_CPUSupport.SSE41 := (Reg.ECX and (1 shl 19)) <> 0;
TDEC_CPUSupport.SSE42 := (Reg.ECX and (1 shl 20)) <> 0;
TDEC_CPUSupport.AES := (Reg.ECX and (1 shl 25)) <> 0;
TDEC_CPUSupport.AVX := (Reg.ECX and (1 shl 28)) <> 0;
TDEC_CPUSupport.RDRand := (Reg.ECX and (1 shl 30)) <> 0;
end;

if NIds >= 7 then
begin
Reg := GetCPUID(7, 0);
TDEC_CPUSupport.AVX2 := (Reg.EBX and (1 shl 5)) <> 0;
TDEC_CPUSupport.RDSeed := (Reg.EBX and (1 shl 18)) <> 0;
end;
end;

{$ELSE}

function GetCPUID(ALeaf: Cardinal; ASubLeaf: Cardinal = 0): TCPUIDRec;
begin
Result.EAX := 0;
Result.EBX := 0;
Result.ECX := 0;
Result.EDX := 0;
end;

{$IFEND}

initialization
TDEC_CPUSupport.AES := False;
TDEC_CPUSupport.AVX := False;
TDEC_CPUSupport.AVX2 := False;
TDEC_CPUSupport.SSE := False;
TDEC_CPUSupport.SSE2 := False;
TDEC_CPUSupport.SSE3 := False;
TDEC_CPUSupport.SSE41 := False;
TDEC_CPUSupport.SSE42 := False;
TDEC_CPUSupport.RDRand := False;
TDEC_CPUSupport.RDSeed := False;
{$IF Defined(x64) or Defined(x86)}
InitFlags;
{$IFEND}
end.
21 changes: 15 additions & 6 deletions Source/DECCipherBase.pas
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{*****************************************************************************
{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
Expand Down Expand Up @@ -183,7 +183,8 @@ interface
cmCFSx, // CFS on Blocksize bytes
cmECBx, // Electronic Code Book
cmGCM, // Galois Counter Mode
cmCCM // Counter with CBC-MAC Mode
cmCCM, // Counter with CBC-MAC Mode
cmPoly1305 // Poly1305 AEAD (typically ChaCha20-Poly1305 / XChaCha20-Poly1305)
{$IFDEF DEC3_CMCTS}
,cmCTS3 // double CBC, with less secure padding of truncated final block
// for DEC 3.0 compatibility only (see DECOptions.inc)
Expand Down Expand Up @@ -900,7 +901,7 @@ procedure SetDefaultCipherClass(CipherClass: TDECCipherClass);

function IsAuthenticatedBlockMode(BlockMode: TCipherMode): Boolean;
begin
Result := BlockMode = cmGCM;
Result := BlockMode in [cmGCM, cmCCM, cmPoly1305];
end;

{ TDECCipher }
Expand Down Expand Up @@ -1055,7 +1056,7 @@ procedure TDECCipher.Init(const Key; Size: Integer; const IVector;
if (Size > Context.KeySize) and (not (ctNull in Context.CipherType)) then
raise EDECCipherException.CreateRes(@sKeyMaterialTooLarge);

if (FInitVectorSize > FBufferSize) and (not (FMode = cmGCM)) then
if (FInitVectorSize > FBufferSize) and (not (FMode in [cmGCM, cmPoly1305])) then
raise EDECCipherException.CreateRes(@sIVMaterialTooLarge);

DoInit(Key, Size);
Expand All @@ -1077,8 +1078,16 @@ procedure TDECCipher.Init(const Key; Size: Integer; const IVector;
// Restore backup fo FBuffer
Move(FAdditionalBufferBackup^, FAdditionalBuffer^, FAdditionalBufferSize);
end
else
Move(IVector, FInitializationVector^, IVectorSize);
else if IVectorSize > 0 then
begin
// cmPoly1305 may pass IVs larger than FBufferSize (e.g. XChaCha 24-byte
// nonce). Full IV is available via OriginalInitVector in OnAfter; only copy
// what fits into the internal IV buffer.
if IVectorSize > FBufferSize then
Move(IVector, FInitializationVector^, FBufferSize)
else
Move(IVector, FInitializationVector^, IVectorSize);
end;

OnAfterInitVectorInitialization(OriginalInitVector);

Expand Down
14 changes: 8 additions & 6 deletions Source/DECCipherFormats.pas
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{*****************************************************************************
{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
Expand Down Expand Up @@ -732,7 +732,9 @@ function TDECFormattedCipher.EncodeBytes(const Source: TBytes): TBytes;
if Length(Result) > 0 then
Encode(Source[0], Result[0], Length(Source))
else
if (FMode = cmGCM) then
// AAD-only authenticated modes still need a zero-length encode pass so the
// auth object can absorb AAD (tag finalized later in Done).
if IsAuthenticatedBlockMode(FMode) then
EncodeGCM(nil, nil, 0);
end;

Expand All @@ -749,13 +751,13 @@ function TDECFormattedCipher.DecodeBytes(const Source: TBytes): TBytes;

if Length(Result) > 0 then
begin
if (FMode = cmGCM) then
if IsAuthenticatedBlockMode(FMode) then
SetLength(Result, Length(Source));

Decode(Source[0], Result[0], Length(Source));
end
else
if (FMode = cmGCM) then
if IsAuthenticatedBlockMode(FMode) then
DecodeGCM(nil, nil, 0);

if not (FPaddingClass = nil) then
Expand Down Expand Up @@ -805,7 +807,7 @@ procedure TDECFormattedCipher.DoEncodeDecodeStream(const Source, Dest: TStream;
SetLength(Buffer, DataSize);

outBuffer := Buffer;
if (FMode = cmGCM) then
if IsAuthenticatedBlockMode(FMode) then
SetLength(outBuffer, Length(Buffer));

while (DataSize > 0) or doStartOnlyPadding do
Expand Down Expand Up @@ -871,7 +873,7 @@ procedure TDECFormattedCipher.DoEncodeDecodeStream(const Source, Dest: TStream;
end;
end
else
if (FMode = cmGCM) then
if IsAuthenticatedBlockMode(FMode) then
begin
Buffer := nil;
CipherProc(Buffer, Buffer, 0);
Expand Down
Loading