1

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?

1
  • Can you try return abi.encodePacked(r, s, v); in function fromVRS?
    – 0xSanson
    Aug 27, 2022 at 11:17

2 Answers 2

1

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)))
}
0

EDIT: Nevermind! I was encoding as v, r, s when it should have been r, s, v - Fixed that and it works perfectly.

I'm trying this as well, but it seems like when ECDSA.tryRecover(bytes32 hash, bytes memory signature) splits the sig back out into v, r, s, it is off by two digits.

domainTypedMessage: 0x81ecaf5bca8e50573e0183ad582d6b6426bd988c9c7fd40c529bea86232136c8 PrivateKey doing signing: 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef

Signature (uint8, bytes32, bytes32):

v: 28
r: 0x4509ccf4268a2473492289ad0570117607db35455b8da02047c32921c7fd9d23
s: 0x74d70e4a1e9f0a465352c3394244c980441aba26b8909e100bae35970081ddff

abiEncodePackedSig (uint8,bytes32,bytes32 ==> bytes):

0x1c4509ccf4268a2473492289ad0570117607db35455b8da02047c32921c7fd9d2374d70e4a1e9f0a465352c3394244c980441aba26b8909e100bae35970081ddff

But when following the trace to the values input to ECDSA.tryRecover(hash,v,r,s), I'm seeing this:

bytes32 hash: 0x81ecaf5bca8e50573e0183ad582d6b6426bd988c9c7fd40c529bea86232136c8
v: 255
r: 12786739849059462053674000992243531224588822583476003318711349217042056609181
s: 16037388220638135921918834900424162121165685228342423020224374679854836122077

When I convert those back to hexadecimal using this, this is what I get:

v: FF
r: 1C4509CCF4268A2473492289AD0570117607DB35455B8DA02047C32921C7FD9D
s: 2374D70E4A1E9F0A465352C3394244C980441ABA26B8909E100BAE35970081DD

We can see that 0x4509 is the first portion of the r, but it's keeping a 1C in there for some reason. Same goes for s where the first two digits are 23 when it should be the 74

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.