I'm trying to call a contract method that checks a signature against the chain ID, contract address, caller, and params. The contract lives at KT1XoT3RTkXCN2y25e635WeEmjN4xszQWtq6 on mainnet, and here is the code. (It's the Hic Et Nunc SwapV2 contract modified to restrict purchases by private key using signatures.)

First I called the swap method:

    def swap(self, params):
        sp.verify((params.objkt_amount > 0) & ((params.royalties >= 0) & (params.royalties <= 250)))
        self.fa2_transfer(self.data.objkt, sp.sender, sp.self_address, params.objkt_id, params.objkt_amount)
        self.data.swaps[self.data.counter] = sp.record(issuer=sp.sender, objkt_amount=params.objkt_amount, objkt_id=params.objkt_id, xtz_per_objkt=params.xtz_per_objkt, royalties=params.royalties, creator=params.creator, pubKey=params.pubKey)
        self.data.counter += 1

with pubkey edpkuEFWHq3MWMjaZo9aEiYPno2feDb745aYepH7DWDbzBFqbxt4wF. swaps now has a single entry with id 1.

The buy method I'm now calling starts with this code in SmartPy that checks the sig:

    def collect(self, params):
        swap = self.data.swaps[params.swap_id]

        toSign = sp.record(chain = sp.chain_id, contract = sp.self_address, sender = sp.sender, swap = params.swap_id)
        packed = sp.pack(toSign)
        ok = sp.check_signature(swap.pubKey, params.signature, packed)
        sp.if ~ok:
          sp.failwith("Bad sig")

Ok. All the above works in a SmartPy unit test (full code here).

@sp.add_test(name = "Marketplace")
def test():
    # define a contract
    c1 = Marketplace(sp.address("KT1VCT6YWNZGKSnmjadGZfKx79uCN1njJ7ze"), sp.map({ 1: "A String" }, tkey = sp.TNat, tvalue = sp.TString), sp.address("tz1cQe16CdsBZnGz4awrh4p3u1xK7o5QApd5"), 10)
    scenario  = sp.test_scenario()
    scenario += c1
    alice = sp.test_account("Alice")
    bob = sp.test_account("Bob")
    c1.swap(objkt_id = 1, objkt_amount = 1, royalties = 10, creator = sp.address("tz1cQe16CdsBZnGz4awrh4p3u1xK7o5QApd5"), pubKey = bob.public_key, xtz_per_objkt = sp.tez(1)).run(sender = bob)
    toSign = sp.record(chain = sp.chain_id, contract = c1.address, sender = bob.address, swap = 500000)
    packed = sp.pack(toSign)
    sig = sp.make_signature(
      secret_key = bob.secret_key,
      message = packed,
      message_format = 'Raw'
    c1.collect(swap_id = 500000, signature = sig).run(sender = bob, amount = sp.tez(1))

But now I'm trying to do it for real using Taquito. This answer told me how to pack the data.

const { ChainIds } = require('@taquito/taquito')
const { InMemorySigner } = require('@taquito/signer')
const { bytes2Char, char2Bytes } = require('@taquito/utils')
const { packDataBytes } = require('@taquito/michel-codec')

;(async () => {
// testing pub-priv keypair I don't mind sharing
const pubKey = "edpkuEFWHq3MWMjaZo9aEiYPno2feDb745aYepH7DWDbzBFqbxt4wF"
const signer = new InMemorySigner("edskRkGz8EzThsHTrtwpU2vL6XYa4o5XBHwuWQ6P3Z8vL9uhi2YvRMfrJFZcVFLwjN753UrorY62XAgAdTFoV2vPzm4vqDSXQQ");

// We'll send this as tz1Tb7cHhKeyKRYU9CQTALedbmYQWzdKeUSz
const data = {
    prim: 'Pair', args: [{ string: ChainIds.MAINNET }, { string: 'KT1XoT3RTkXCN2y25e635WeEmjN4xszQWtq6' }, { string: "tz1Tb7cHhKeyKRYU9CQTALedbmYQWzdKeUSz" }, { int: '1' }]
const typ = {
  prim: 'pair', args: [{ prim: 'chain_id' }, { prim: 'address' }, { prim:"address" }, {prim: 'int'}]
let bytes = packDataBytes(data, typ).bytes
const signature = await signer.sign(bytes);

This yields

  signature: {
    bytes: '0507070a000000047a06a77007070a0000001601feb0d69309e346326f49161325bc134604f8ef190007070a0000001600005733dcd6c0d5c1ce9208cf1176b0e04608dfb15e0001',
    sig: 'sigSmjBUs7tBXFe5WyrdJveGJ4uS53eBbf5fyTWU9sA6PpK7DYuMnZ85xTnwyWFun1q1PhcJMkYXs1xWvifjUtTYpLGPhABg',
    prefixSig: 'edsigtcbCJY62A7iHhGWGzfA7bZaagG9ycpUFTMjCEp9rASQmcwA9QFheD9CNqYbJMNxNGYsKXp97P9pnhJxvKsprebtSDFdSDp',
    sbytes: '0507070a000000047a06a77007070a0000001601feb0d69309e346326f49161325bc134604f8ef190007070a0000001600005733dcd6c0d5c1ce9208cf1176b0e04608dfb15e0001248fd541b9a5557e75e93e130643390496f1b22b6335d7fb33932178a79208d05d849db4135ab839a4cbd6efc7d2b665b0e524673ebcd26bb5948e7533a28101'

I tried using that edsig... value as my signature, which is what the SmartPy test sig looked like. Called collect in bettercall.dev. Contract says "bad sig". Am I packing the data wrongly or something? I realize this is a very broad question, just wondering if there's an obvious newbie mistake I'm making.

Update from 2AM findings

I tried passing the mainnet parameters into the SmartPy test and obtained this packed value that resulted in the correct signature: 05070707070a000000047a06a7700a0000001601feb0d69309e346326f49161325bc134604f8ef190007070a0000001600005733dcd6c0d5c1ce9208cf1176b0e04608dfb15e0001. The contract call succeeded using the sig from that! :)

But my Taquito code is yielding 0507070a000000047a06a77007070a0000001601feb0d69309e346326f49161325bc134604f8ef190007070a0000001600005733dcd6c0d5c1ce9208cf1176b0e04608dfb15e0001 as mentioned above. Looks the same except for a few leading "07" bytes. Curiously, this answer on packing data in Taquito shows a different output from what I'm getting, with the same difference of leading bytes. So I suspect Taquito isn't packing data the same way anymore.


1 Answer 1


The layout is probably wrong. Make sure you use the same record layout in both locations.

    toSign = sp.set_type_expr(
            chain = sp.chain_id,
            contract = sp.self_address,
            sender = sp.sender,
            swap = params.swap_id
            chain = sp.TChainId,
            contract = sp.TAddress,
            sender = sp.TAddress,
            swap = sp.TNat


swap_id is of type nat: (In this case, it should not influence the packed bytes)

const typ = {
  prim: 'pair', args: [{ prim: 'chain_id' }, { prim: 'address' }, { prim:"address" }, {prim: 'nat'}]

Hope this example helps: https://github.com/ecadlabs/taquito/blob/1abc6b0fc96c165434e53e3008b7676e3796f337/integration-tests/contract-permits.spec.ts#L70

The test does what you are trying to achieve.

  • 1
    This fixed it. Taquito ended up giving me the same value as this SmartPy toSign value. Not really sure why, but thank you, I'll take it!
    – sudo
    Commented Apr 11, 2022 at 3:19

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.