Skip to content

Commit

Permalink
add falcon interface to test both, precompiled and solidity implement…
Browse files Browse the repository at this point in the history
…ations

update lacchain besu image
refactor: unifies testing cases
route kat tests through a common interface

Signed-off-by: eum602 <eum602@gmail.com>
  • Loading branch information
eum602 committed Oct 26, 2023
1 parent ae60f2f commit af73b8a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 101 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
services:
besu:
image: hyperledger/besu:23.1.0
image: ghcr.io/lacchain/lacchain-besu:23.7.3.0-rc2-amd64
env:
BESU_NETWORK: dev
BESU_MIN_GAS_PRICE: 0
Expand Down
16 changes: 8 additions & 8 deletions contracts/Falcon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ contract Falcon
////////////////////////////////////////
//
////////////////////////////////////////
function keccak_inc_absorb(uint32 r, uint8[] memory pInput, uint32 cbInput) public payable
function keccak_inc_absorb(uint32 r, bytes memory pInput, uint32 cbInput) public payable
{
uint32 i;
uint32 msg_offset;
Expand Down Expand Up @@ -1092,7 +1092,7 @@ contract Falcon
////////////////////////////////////////
//
////////////////////////////////////////
function OQS_SHA3_shake256_inc_absorb(uint8[] memory input, uint32 inlen) public payable
function OQS_SHA3_shake256_inc_absorb(bytes memory input, uint32 inlen) public payable
{
keccak_inc_absorb(SHAKE256_RATE, input, inlen);
}
Expand Down Expand Up @@ -1262,7 +1262,7 @@ contract Falcon
////////////////////////////////////////
//
////////////////////////////////////////
function PQCLEAN_FALCON512_CLEAN_modq_decode(uint16[] memory pX, uint16 logn, uint8[] memory pInput, uint16 In_offset, uint16 cbInputMax) public pure returns (uint16)
function PQCLEAN_FALCON512_CLEAN_modq_decode(uint16[] memory pX, uint16 logn, bytes memory pInput, uint16 In_offset, uint16 cbInputMax) public pure returns (uint16)
{
uint16 n;
uint16 In_len;
Expand Down Expand Up @@ -1441,11 +1441,11 @@ contract Falcon
// const uint8_t* pPublicKey)
////////////////////////////////////////
function verify (uint8 signatureType,
uint8[] memory pSignatureBuf,
bytes calldata pSignatureBuf,
uint16 cbSignatureBuf,
uint8[] memory pMessage,
bytes memory pMessage,
uint16 cbMessage,
uint8[] memory pPublicKey,
bytes memory pPublicKey,
uint16 cbPublicKey) public payable returns (int16)
{

Expand Down Expand Up @@ -1554,7 +1554,7 @@ contract Falcon
// Start of Verification Proper
////////////////////////////////////////////////

uint8[] memory pNonce = new uint8[](NONCELEN); // uint8[NONCELEN] memory pNonce;
bytes memory pNonce = new bytes(NONCELEN); // uint8[NONCELEN] memory pNonce;
uint16[] memory pWordArrayH; // uint16[512]
int16[] memory pSignedWordArraySig; // int16[512]

Expand All @@ -1577,7 +1577,7 @@ contract Falcon
uint sourceOffset = 1;
for (ii=0; ii<NONCELEN; ii++)
{
pNonce[ii] = uint8(pSignatureBuf[sourceOffset + ii]);
pNonce[ii] = pSignatureBuf[sourceOffset + ii];
}

sourceOffset = 1 + NONCELEN;
Expand Down
24 changes: 24 additions & 0 deletions contracts/FalconWrap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

import "./Falcon.sol";

contract FalconWrap is Falcon {
function verify(
bytes calldata signature,
bytes calldata publicKey,
bytes calldata message
) public returns (int16) {
uint8 signatureType = (uint8(signature[0]) >> 5) & 0x03;
return
verify(
signatureType,
signature,
uint16(signature.length),
message,
uint16(message.length),
publicKey,
uint16(publicKey.length)
);
}
}
22 changes: 22 additions & 0 deletions contracts/Interface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//SPDX-License-Identifier: APACHE2
pragma solidity ^0.7.0;

contract FalconInterface {
function verify(
bytes calldata signature,
bytes calldata publicKey,
bytes calldata message,
address falconVerifier
) public returns (bool isValid) {
(bool success, bytes memory verifies) = address(falconVerifier).call(
abi.encodeWithSignature(
"verify(bytes,bytes,bytes)",
signature,
publicKey,
message
)
);
require(success && verifies.length == 32, "Invalid signature");
return verifies[31] == 0;
}
}
167 changes: 76 additions & 91 deletions test/falcon512-KAT.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,106 @@ const Test = require('mocha/lib/test');

const falcConsts = require('./falcon_constants.js');

async function callAndCheck(
signature_array,
pubKey_array,
message_array,
contractAddress,
isValid,
falconCommonInterface
) {
let verifyArgs = [
signature_array,
pubKey_array,
message_array,
contractAddress,
];

let ret = await falconCommonInterface.callStatic.verify.apply(
null,
verifyArgs
);
assert.equal(ret, isValid);
}

async function verifyKat(kat, falconCommonInterface, targetImplementationContractAddress) {
let signatureType = falcConsts.FALCON_SIG_1_COMPRESSED; // FALCON_SIG_0_INFERRED, FALCON_SIG_1_COMPRESSED, FALCON_SIG_2_PADDED, FALCON_SIG_3_CT
const msg = Buffer.from(kat.msg, 'hex');
const mlen = parseInt(kat.mlen);
const pk = Buffer.from(kat.pk, 'hex');
const sm = Buffer.from(kat.sm, 'hex');
const smlen = parseInt(kat.smlen);

// Deconstruct KAT sm field which is in "3.11.6 NIST API" format
const smSigLen = sm.readInt16BE(0); // Convert a 2 byte BigEndian value to a number
const smNonce = sm.slice(2, 42); // Skip over the 2 bytes for the smSigLen
const smMsg = sm.slice(2 + 40, smlen-2-40-smSigLen); // Not used - Should be identical to msg
const smSig = sm.slice(2 + 40 + mlen); // Skip 2 bytes for signatureSize + 40 bytes for nonce + mlen bytes for message.
const smSigHdrByte = smSig.slice(0, 1); // Header Byte
const smSigRaw = smSig.slice(1); // Raw Signature

// Construct Signature field to send to the contract which expects it in "3.11.3 Signatures" format
var argSigHdrBuf = Buffer.from([0x39]);
const argSig = Buffer.concat([argSigHdrBuf, smNonce, smSigRaw]);

// calling both precompiled and solidity through a common interface.
const signature_array = Array.from(argSig);
const message_array = Array.from(msg);
const pubKey_array = Array.from(pk);
await callAndCheck(
signature_array,
pubKey_array,
message_array,
targetImplementationContractAddress,
true,
falconCommonInterface
);
}

describe("Falcon", async () =>
{
const suite = describe("falcon512-KAT - Known Answer Tests", async () =>
{
let falconSolidityImpl;
let falconCommonInterface;
let falconInstance;
let precompiledContractAddress = falcConsts.FALCON_PRECOMPILED_ADDRESS;

before(async () =>
{
const kats = await parseKats(fs.createReadStream('test/falcon512-KAT.rsp'));
const Falcon = await hre.ethers.getContractFactory('Falcon')
const FalconSolidityImpl = await hre.ethers.getContractFactory(
"FalconWrap"
);
const FalconCommonInterface = await hre.ethers.getContractFactory(
"FalconInterface"
);
falconInstance = await Falcon.deploy();
falconSolidityImpl = await FalconSolidityImpl.deploy();
const solidityFalconAddress = falconSolidityImpl.address;
falconCommonInterface = await FalconCommonInterface.deploy();

//kats.slice(0, 5).forEach(kat => // Tests 0,1,2,3,4
kats.forEach(kat => // All Tests
{
suite.addTest(new Test(`KAT test ${kat.count}`, async() =>
suite.addTest(new Test(`KAT test ${kat.count} (solidity)`, async() =>
{
let signatureType = falcConsts.FALCON_SIG_1_COMPRESSED; // FALCON_SIG_0_INFERRED, FALCON_SIG_1_COMPRESSED, FALCON_SIG_2_PADDED, FALCON_SIG_3_CT
const msg = Buffer.from(kat.msg, 'hex');
const mlen = parseInt(kat.mlen);
const pk = Buffer.from(kat.pk, 'hex');
const pklen = pk.length;
const sm = Buffer.from(kat.sm, 'hex');
const smlen = parseInt(kat.smlen);

// Deconstruct KAT sm field which is in "3.11.6 NIST API" format
const smSigLen = sm.readInt16BE(0); // Convert a 2 byte BigEndian value to a number
const smNonce = sm.slice(2, 42); // Skip over the 2 bytes for the smSigLen
const smMsg = sm.slice(2 + 40, smlen-2-40-smSigLen); // Not used - Should be identical to msg
const smSig = sm.slice(2 + 40 + mlen); // Skip 2 bytes for signatureSize + 40 bytes for nonce + mlen bytes for message.
const smSigHdrByte = smSig.slice(0, 1); // Header Byte
const smSigRaw = smSig.slice(1); // Raw Signature

// Construct Signature field to send to the contract which expects it in "3.11.3 Signatures" format
var argSigHdrBuf = Buffer.from([0x39]);
const argSig = Buffer.concat([argSigHdrBuf, smNonce, smSigRaw]);
const argSigLen = argSigHdrBuf.length + smNonce.length + smSigRaw.length;

//console.log("==> argSigHdrBuf[%d]: 0x%s", argSigHdrBuf.length, argSigHdrBuf.toString('hex'));
//console.log("==> smSigRawLen = smlen(%d) - smSigLen.len(2) - nonceNen(40) - mlen(%d) - sigHdrByte(1) = %d [%d]",
// smlen, mlen,
// (smlen - 2 - 40 - mlen - 1),
// smSigRaw.length );
//console.log("==> argSig[%d]: 0x%s", argSigLen, argSig.toString('hex'));

if (1)
{
const contractReturn = await falconInstance.callStatic.verify(signatureType, Array.from(argSig), argSigLen, Array.from(msg), mlen, Array.from(pk), pklen);
if (contractReturn != falcConsts.FALCON_ERR_SUCCESS)
{
let ret = getFalconReturnValue(contractReturn);
let errorReasonCode = getReasonCode(contractReturn);
let errorStr = "ERROR: falcon.verify returned " + ret + " (" + falcConsts.FALCON_ERR_Description[Math.abs(ret)] + ") [reason: " + errorReasonCode + "]";
console.log(errorStr);
}
const falconReturn = getFalconReturnValue(contractReturn);
assert.equal(falconReturn, falcConsts.FALCON_ERR_SUCCESS, `${falcConsts.FALCON_ERR_LongDescription[Math.abs(falconReturn)]}`);
}
else
{
let expectedRet = falcConsts.FALCON_ERR_SUCCESS;
const signature_array = argSig.toJSON().data;
const message_array = msg.toJSON().data;
const pubKey_array = pk.toJSON().data;
let verifyArgs = [signatureType, signature_array, argSigLen, message_array, mlen, pubKey_array, pklen];
let ret = await falconInstance.callStatic.verify.apply(null, verifyArgs);
let errorReasonCode = getReasonCode(ret);
ret = getFalconReturnValue(ret);
let errorStr = "ERROR: falcon.verify expected " + expectedRet + " (" + falcConsts.FALCON_ERR_Description[Math.abs(expectedRet)] + "), but got " + ret + " (" + falcConsts.FALCON_ERR_Description[Math.abs(ret)] + ") [reason: " + errorReasonCode + "]";
if (ret != expectedRet) console.log(errorStr);
assert.equal(ret, expectedRet, errorStr);
let tx = await falconInstance.verify.apply(null, verifyArgs);
let receipt = await tx.wait();
assert.equal(receipt.status, true);
}
await verifyKat(kat, falconCommonInterface, solidityFalconAddress);
}));
});


suite.addTest(new Test(`KAT test ${kat.count} (precompiled)`, async() =>
{
await verifyKat(kat, falconCommonInterface, precompiledContractAddress);
}));
});
});

it.skip("dummy", () =>
{
// Mocha needs at least one explicit declaration otherwise it ignores the whole suite.
});


const parseKats = async (katStream) =>
{
const rl = readline.createInterface( {input: katStream, crlfDelay: Infinity} );
Expand All @@ -117,35 +131,6 @@ describe("Falcon", async () =>
}
return kats;
}

const getFalconReturnValue = (ret) =>
{
var isNegative = false;
if (ret < 0)
{
isNegative = true;
ret = Math.abs(ret);
}
var remainder = ret % 10;
if (isNegative)
remainder = -remainder;
return remainder;
}

const getReasonCode = (ret) =>
{
var isNegative = false;
if (ret < 0)
{
isNegative = true;
ret = Math.abs(ret);
}
var remainder = ret % 10;
var quotient = ret - remainder; // or maybe Math.floor(ret/10) or trunc(ret/10)
if (isNegative)
quotient = -quotient;
return quotient;
}
});
});

Expand Down
5 changes: 4 additions & 1 deletion test/falcon_constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const FALCON_SIG_3_CT = 3; // Fixed-size format amenable to constant-tim
// the 'CT' format also prevents information about the signature value and the signed data hash to leak through timing-based side channels (this feature is rarely needed).
const FALCON_SIG_4_INVALID = 4;

const FALCON_PRECOMPILED_ADDRESS = "0x0000000000000000000000000000000000000014";

module.exports = {
FALCON_ERR_SUCCESS,
FALCON_ERR_RANDOM,
Expand All @@ -56,7 +58,8 @@ module.exports = {
FALCON_SIG_1_COMPRESSED,
FALCON_SIG_2_PADDED,
FALCON_SIG_3_CT,
FALCON_SIG_4_INVALID
FALCON_SIG_4_INVALID,
FALCON_PRECOMPILED_ADDRESS
};


Expand Down

0 comments on commit af73b8a

Please sign in to comment.