Solidity – How to Get Signature Bytes from V, R, S (ECDSA)

ecdsasignaturesolidity

How do I get the signature bytes from V, R, S?
Here is what I have tried:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;

import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import "openzeppelin/utils/cryptography/ECDSA.sol";

library Signature {
    error InvalidSignature();

    function toVRS(bytes memory signature)
        internal
        pure
        returns (
            uint8 v,
            bytes32 r,
            bytes32 s
        )
    {
        if (signature.length == 65) {
            // ecrecover takes the signature parameters,
            // and the only way to get them currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
        } else {
            revert InvalidSignature();
        }
    }

    function fromVRS(
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (bytes memory) {
        // v ++ (length(r) + 0x80 ) ++ r ++ (length(s) + 0x80) ++ s
        // v ++ r ++ s
        return abi.encodePacked(v, r, s);
    }
}

contract SignTest is Test {
    using ECDSA for bytes32;

    uint256 private immutable alicePk = 1;
    uint256 private immutable bobPk = 2;
    address private immutable alice = vm.addr(1);
    address private immutable bob = vm.addr(2);

    function setUp() public {}


    function testSignature() public {
        bytes32 hash = keccak256("signed by Alice");
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, hash);
        address signer = ecrecover(hash, v, r, s);
        assertEq(alice, signer);

        bytes memory signature = Signature.fromVRS(v, r, s);
        (uint8 v1, bytes32 r1, bytes32 s1) = Signature.toVRS(signature);
        signer = ecrecover(hash, v1, r1, s1);
        assertEq(alice, signer);
    }

    function testRecoverVRS() public {
        bytes32 h = keccak256(abi.encodePacked(uint256(1), "whatever"));
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, h);
        bytes memory signature = Signature.fromVRS(v, r, s);
        console2.log("signature length: %d", signature.length);
        (uint8 v1, bytes32 r1, bytes32 s1) = Signature.toVRS(signature);

        assertEq(v, v1);
        assertEq(r, r1);
        assertEq(s, s1);
    }
}

But this doesn't work (tests fail).
Ideally, I'd love to keep the signature as v,r,s, but I want bytes for testing. I'm using foundry and I want to write a test for a function that takes bytes memory signature (in solidity).

Is there a function v,r,s -> bytes?

Best Answer

As @0xSanson mentioned in the comment above it should be abi.encodePackaged(r, s, v). In the same order in which the data is read:

assembly {
    r := mload(add(signature, 0x20))
    s := mload(add(signature, 0x40))
    v := byte(0, mload(add(signature, 0x60)))
}
Related Topic