From af73b8a3a96bbd4b57fd4e8347bf4eab4f6ebebb Mon Sep 17 00:00:00 2001 From: eum602 Date: Wed, 25 Oct 2023 19:31:51 -0500 Subject: [PATCH] add falcon interface to test both, precompiled and solidity implementations update lacchain besu image refactor: unifies testing cases route kat tests through a common interface Signed-off-by: eum602 --- .github/workflows/test.yml | 2 +- contracts/Falcon.sol | 16 ++-- contracts/FalconWrap.sol | 24 ++++++ contracts/Interface.sol | 22 +++++ test/falcon512-KAT.js | 167 +++++++++++++++++-------------------- test/falcon_constants.js | 5 +- 6 files changed, 135 insertions(+), 101 deletions(-) create mode 100644 contracts/FalconWrap.sol create mode 100644 contracts/Interface.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fc7dfca..aa9d9a17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/contracts/Falcon.sol b/contracts/Falcon.sol index ca65a982..9af1e794 100644 --- a/contracts/Falcon.sol +++ b/contracts/Falcon.sol @@ -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; @@ -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); } @@ -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; @@ -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) { @@ -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] @@ -1577,7 +1577,7 @@ contract Falcon uint sourceOffset = 1; for (ii=0; ii> 5) & 0x03; + return + verify( + signatureType, + signature, + uint16(signature.length), + message, + uint16(message.length), + publicKey, + uint16(publicKey.length) + ); + } +} diff --git a/contracts/Interface.sol b/contracts/Interface.sol new file mode 100644 index 00000000..2edb2985 --- /dev/null +++ b/contracts/Interface.sol @@ -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; + } +} diff --git a/test/falcon512-KAT.js b/test/falcon512-KAT.js index fd2a6df8..2b7ada5b 100644 --- a/test/falcon512-KAT.js +++ b/test/falcon512-KAT.js @@ -6,84 +6,99 @@ 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", () => @@ -91,7 +106,6 @@ describe("Falcon", async () => // 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} ); @@ -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; - } }); }); diff --git a/test/falcon_constants.js b/test/falcon_constants.js index 91b70577..dd7d334e 100644 --- a/test/falcon_constants.js +++ b/test/falcon_constants.js @@ -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, @@ -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 };