Filcoin的交易根据地址的签名类型也分为两种, Secp256k1Bls 类型。

本文讲述的是 Secp256k1 签名.

同样,如果要看懂代码实现,我们先简单看一下生成交易签名的过过程。

  1. 构造未签名前的交易消息结构
{
 "To": "f1ysweoq6x2xpppjao57igbjlzeanh4mwfbxnacya",
 "From": "f1b3ajnnlm5t5ymbw2kdgkmcs4lpt6i2fo6uxa3ia",
 "Nonce": 10,
 "Value": "1000000000000",
 "GasLimit": 5000000,
 "GasFeeCap": "149315",
 "GasPremium": "149010",
 "Method": 0,
 "Params": ""
},

参数说明:

  • To: 发送地址
  • From: 收件地址
  • Nonce:随机值
  • Value:发送数量
  • GasLimit: 消耗的Gas限制
  • GasFeeCap: Gas费用小费
  • GasPremium:支付的Gas费用
  • Method : 转账0
  • Params: 携带的参数,可为空

这里的Nonce以及Gas的相关数据建议不要hardcode,我们可以通过lotus的相关RPC相关接口来进行获取。

filcoin的gas机制相对于eth引入了小费的概念以及加入了相应惩罚机制,具体细则,可以戳这篇文章了解。

https://www.btcfans.com/article/31807

2、将上述的构造的交易消息对象的字段 加上一个version=0字段 按照顺序进行序列化,将java的string转换成corb的类型,

int => UnsignedInteger,  string=> ByteString

一个序列化的消息对象大致结果如下:

82                                      # array(2)
   8A                                   # array(10)
      00                                # unsigned(0)
      55                                # bytes(21)
         01FD1D0F4DFCD7E99AFCB99A8326B7DC459D32C628 # "\x01\xFD\x1D\x0FM\xFC\xD7\xE9\x9A\xFC\xB9\x9A\x83&\xB7\xDCE\x9D2\xC6("
      55                                # bytes(21)
         011EAF1C8A4BBFEEB0870B1745B1F57503470B7116 # "\x01\x1E\xAF\x1C\x8AK\xBF\xEE\xB0\x87\v\x17E\xB1\xF5u\x03G\vq\x16"
      01                                # unsigned(1)
      44                                # bytes(4)
         000186A0                       # "\x00\x01\x86\xA0"
      19 09C4                           # unsigned(2500)
      42                                # bytes(2)
         0001                           # "\x00\x01"
      42                                # bytes(2)
         0001                           # "\x00\x01"
      00                                # unsigned(0)
      40                                # bytes(0)   # ""
   58 42                                # bytes(66)
      0106398485060CA2A4DEB97027F518F45569360C3873A4303926FA6909A7299D4C55883463120836358FF3396882EE0DC2CF15961BD495CDFB3DE1EE2E8BD3768E01 "\x01\x069\x84\x85\x06\f\xA2\xA4\xDE\xB9p'\xF5\x18\xF4Ui6\f8s\xA409&\xFAi\t\xA7)\x9DLU\x884c\x12\b65\x8F\xF39h\x82\xEE\r\xC2\xCF\x15\x96\e\xD4\x95\xCD\xFB=\xE1\xEE.\x8B\xD3v\x8E\x01"

3、对上述生成的字节进行 Cbor编码,然后拼接CID的固定前缀,再进行32位的Blake2b运算,最终生成cidhash

8a005501fd1d0f4dfcd7e99afcb99a8326b7dc459d32c62855011eaf1c8a4bbfeeb0870b1745b1f57503470b71160144000186a01961a84200014200010040

4、对cidhash 进行签名,这里可用web3j库的消息签名方法对其签名,将获取的签名进行base64编码,最终就得到我们的签名数据,

T0k8WUnq4d8I35j2CXhGlusw2upJ5emAsQ2MEobX89Ys9PtMX129MgGZnzgEofYocLaG8chHtvANo8aVxJtjAAE=

5、 将第一步未签名的消息和签名的数据结构合并,构造成最终的消息对象,通过RPC接口MpoolPush广播出去即可。

{
    "jsonrpc": "2.0",
    "method": "Filecoin.MpoolPush",
    "params": [
        {
            "Message": {
                "Version": 0,
                "To": "f1ysweoq6x2xpppjao57igbjlzeanh4mwfbxnacya",
                "From": "f1b3ajnnlm5t5ymbw2kdgkmcs4lpt6i2fo6uxa3ia",
                "Nonce": 10,
                "Value": "1000000000000",
                "GasLimit": 5000000,
                "GasFeeCap": "149315",
                "GasPremium": "149010",
                "Method": 0,
                "Params": ""
            },
            "Signature": {
                "Type": 1,
                "Data": "T0k8WUnq4d8I35j2CXhGlusw2upJ5emAsQ2MEobX89Ys9PtMX129MgGZnzgEofYocLaG8chHtvANo8aVxJtjAAE="
            }
        }
    ],
    "id": 1
}

下面是签名的过程的代码,仅做参考。


 // 构造交易消息
 FilUnsignedMessageApi unsignedMessageAPI = new FilUnsignedMessageApi();
    unsignedMessageAPI.setFrom("f1b3ajnnlm5t5ymbw2kdgkmcs4lpt6i2fo6uxa3ia");
    unsignedMessageAPI.setTo("f1ysweoq6x2xpppjao57igbjlzeanh4mwfbxnacya");
    unsignedMessageAPI.setGas_limit(433268);
    unsignedMessageAPI.setGas_feecap("151064");
    unsignedMessageAPI.setGas_premium("150776");
    unsignedMessageAPI.setNonce(11);
    unsignedMessageAPI.setValue("10000000000000000");
    unsignedMessageAPI.setMethod(0);
    unsignedMessageAPI.setParams("");

    // 序列化消息
    public  void transaction_serialize(FilUnsignedMessageApi unsignedMessageAPI) {

        FilUnsignedMessage unsignedMessage = try_from(unsignedMessageAPI);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
                new CborEncoder(baos).encode(new CborBuilder()
                                .addArray()
                                .add(unsignedMessage.getVersion())
                                // add string
                                .add(unsignedMessage.getTo())
                                .add(unsignedMessage.getFrom())
                                .add(unsignedMessage.getSequence())
                                .add(unsignedMessage.getValue())
                                .add(unsignedMessage.getGas_limit())
                                .add(unsignedMessage.getGas_feecap())
                                .add(unsignedMessage.getGas_premium())
                                .add(unsignedMessage.getMethod_num())
                                // add integer
                                .add(new co.nstant.in.cbor.model.ByteString(new byte[]{}))
                                .end()
                                .build());
                byte[] encodedBytes = baos.toByteArray();
                byte[] cidHashBytes = getCidHash(encodedBytes);

                String cborHEX = NumericUtil.bytesToHex(encodedBytes);
                String cidHEX = NumericUtil.bytesToHex(cidHashBytes);

                String signData = sign(cidHashBytes);

        } catch (CborException e) {
                e.printStackTrace();
        }
}

public  FilUnsignedMessage try_from(FilUnsignedMessageApi unsignedMessageAPI) {

                FilAddress from = FilAddress.from_str(unsignedMessageAPI.getFrom());
                FilAddress to = FilAddress.from_str(unsignedMessageAPI.getTo());
                FilUnsignedMessage unsignedMessage = new FilUnsignedMessage();
                unsignedMessage.setVersion(new UnsignedInteger(0));
                unsignedMessage.setTo(new co.nstant.in.cbor.model.ByteString(to.getPayload()));
                unsignedMessage.setFrom(new co.nstant.in.cbor.model.ByteString(from.getPayload()));
                unsignedMessage.setSequence(new UnsignedInteger(unsignedMessageAPI.getNonce()));
                co.nstant.in.cbor.model.ByteString valueByteString = null;
                if (new BigInteger(unsignedMessageAPI.getValue()).toByteArray()[0] != 0) {
                    byte[] byte1 = new byte[new BigInteger(unsignedMessageAPI.getValue()).toByteArray().length + 1];
                    byte1[0] = 0;
                    System.arraycopy(new BigInteger(unsignedMessageAPI.getValue()).toByteArray(), 0, byte1, 1, new BigInteger(unsignedMessageAPI.getValue()).toByteArray().length);
                    valueByteString = new co.nstant.in.cbor.model
                            .ByteString(byte1);
                } else {
                    valueByteString = new co.nstant.in.cbor.model
                            .ByteString(new BigInteger(unsignedMessageAPI.getValue()).toByteArray());
                }

                unsignedMessage.setValue(valueByteString);

                unsignedMessage.setGas_limit(new UnsignedInteger(unsignedMessageAPI.getGas_limit()));

                co.nstant.in.cbor.model.ByteString gasFeecapString = null;
                if (new BigInteger(unsignedMessageAPI.getGas_Feecap()).toByteArray()[0] != 0) {
                    byte[] byte2 = new byte[new BigInteger(unsignedMessageAPI.getGas_Feecap()).toByteArray().length + 1];
                    byte2[0] = 0;
                    System.arraycopy(new BigInteger(unsignedMessageAPI.getGas_Feecap()).toByteArray(), 0, byte2, 1, new BigInteger(unsignedMessageAPI.getGas_Feecap()).toByteArray().length);
                    gasFeecapString = new co.nstant.in.cbor.model
                            .ByteString(byte2);
                } else {
                    gasFeecapString = new co.nstant.in.cbor.model
                            .ByteString(new BigInteger(unsignedMessageAPI.getGas_Feecap()).toByteArray());
                }

                unsignedMessage.setGas_feecap(gasFeecapString);

                co.nstant.in.cbor.model.ByteString gasPremiumString = null;
                if (new BigInteger(unsignedMessageAPI.getGas_premium()).toByteArray()[0] != 0) {
                    byte[] byte2 = new byte[new BigInteger(unsignedMessageAPI.getGas_premium()).toByteArray().length + 1];
                    byte2[0] = 0;
                    System.arraycopy(new BigInteger(unsignedMessageAPI.getGas_premium()).toByteArray(), 0, byte2, 1, new BigInteger(unsignedMessageAPI.getGas_premium()).toByteArray().length);
                    gasPremiumString = new co.nstant.in.cbor.model
                            .ByteString(byte2);
                } else {
                    gasPremiumString = new co.nstant.in.cbor.model
                            .ByteString(new BigInteger(unsignedMessageAPI.getGas_premium()).toByteArray());
                }

                unsignedMessage.setGas_premium(gasPremiumString);

                unsignedMessage.setMethod_num(new UnsignedInteger(0));
                unsignedMessage.setParams(new co.nstant.in.cbor.model.ByteString(new byte[0]));
                return unsignedMessage;
            }

            public  byte[] CID_PREFIX = new byte[]{0x01, 0x71, (byte) 0xa0, (byte) 0xe4, 0x02, 0x20};

            /**
             * @param message 交易结构体的序列化字节
             *                通过交易结构体字节获取CidHash
             */
            public  byte[] getCidHash(byte[] message) {
                Blake2b.Param param = new Blake2b.Param();
                param.setDigestLength(32);

                //消息体字节
                byte[] messageByte = Blake2b.Digest.newInstance(param).digest(message);

                int xlen = CID_PREFIX.length;
                int ylen = messageByte.length;

                byte[] result = new byte[xlen + ylen];

                System.arraycopy(CID_PREFIX, 0, result, 0, xlen);
                System.arraycopy(messageByte, 0, result, xlen, ylen);

                byte[] prefixByte = Blake2b.Digest.newInstance(param).digest(result);
                String prefixByteHex = NumericUtil.bytesToHex(prefixByte);

                return prefixByte;
            }

         /**
             * 签名
             *
             */
            public  String sign(byte[] cidHash) {
                ECKeyPair ecKeyPair = ECKeyPair.create(Numeric.toBigInt("你的私钥"));
                org.web3j.crypto.Sign.SignatureData signatureData = org.web3j.crypto.Sign.signMessage(cidHash,
                        ecKeyPair, false);
                byte[] sig = getSignature(signatureData);
                String stringHex = NumericUtil.bytesToHex(sig);
                String base64 = Base64.encodeBase64String(sig);

                return base64;
            }

            /**
             * 获取签名
             *
             * @param signatureData
             * @return
             */
            private  byte[] getSignature(org.web3j.crypto.Sign.SignatureData signatureData) {
                byte[] sig = new byte[65];
                System.arraycopy(signatureData.getR(), 0, sig, 0, 32);
                System.arraycopy(signatureData.getS(), 0, sig, 32, 32);
//                sig[64] = (byte)0;
                sig[64] = (byte) ((signatureData.getV() & 0xFF) - 27);
                return sig;
            }

FilUnsignedMessage.java

import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.UnsignedInteger;

public class FilUnsignedMessage {

        private UnsignedInteger version;
        private ByteString from;
        private ByteString to;
        private UnsignedInteger sequence;
        private ByteString value;
        private ByteString gas_premium;
        private ByteString gas_feecap;
        private UnsignedInteger gas_limit;
        private UnsignedInteger method_num;
        private ByteString params; //空数组

        public UnsignedInteger getVersion() {
            return version;
        }

        public void setVersion(UnsignedInteger version) {
            this.version = version;
        }

        public ByteString getFrom() {
            return from;
        }

        public void setFrom(ByteString from) {
            this.from = from;
        }

        public ByteString getTo() {
            return to;
        }

        public void setTo(ByteString to) {
            this.to = to;
        }

        public UnsignedInteger getSequence() {
            return sequence;
        }

        public void setSequence(UnsignedInteger sequence) {
            this.sequence = sequence;
        }

    public ByteString getGas_premium() {
        return gas_premium;
    }

    public void setGas_premium(ByteString gas_premium) {
        this.gas_premium = gas_premium;
    }

    public ByteString getGas_feecap() {
        return gas_feecap;
    }

    public void setGas_feecap(ByteString gas_feecap) {
        this.gas_feecap = gas_feecap;
    }

        public ByteString getValue() {
            return value;
        }

        public void setValue(ByteString value) {
            this.value = value;
        }

        public UnsignedInteger getGas_limit() {
            return gas_limit;
        }

        public void setGas_limit(UnsignedInteger gas_limit) {
            this.gas_limit = gas_limit;
        }

        public UnsignedInteger getMethod_num() {
            return method_num;
        }

        public void setMethod_num(UnsignedInteger method_num) {
            this.method_num = method_num;
        }

        public ByteString getParams() {
            return params;
        }

        public void setParams(ByteString params) {
            this.params = params;
        }

}

FilUnsignedMessageApi.java

package com.jasonz.filwallet;

import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.UnsignedInteger;

public class FilUnsignedMessage {

        private UnsignedInteger version;
        private ByteString from;
        private ByteString to;
        private UnsignedInteger sequence;
        private ByteString value;
        private ByteString gas_premium;
        private ByteString gas_feecap;
        private UnsignedInteger gas_limit;
        private UnsignedInteger method_num;
        private ByteString params; //空数组

        public UnsignedInteger getVersion() {
            return version;
        }

        public void setVersion(UnsignedInteger version) {
            this.version = version;
        }

        public ByteString getFrom() {
            return from;
        }

        public void setFrom(ByteString from) {
            this.from = from;
        }

        public ByteString getTo() {
            return to;
        }

        public void setTo(ByteString to) {
            this.to = to;
        }

        public UnsignedInteger getSequence() {
            return sequence;
        }

        public void setSequence(UnsignedInteger sequence) {
            this.sequence = sequence;
        }

    public ByteString getGas_premium() {
        return gas_premium;
    }

    public void setGas_premium(ByteString gas_premium) {
        this.gas_premium = gas_premium;
    }

    public ByteString getGas_feecap() {
        return gas_feecap;
    }

    public void setGas_feecap(ByteString gas_feecap) {
        this.gas_feecap = gas_feecap;
    }

        public ByteString getValue() {
            return value;
        }

        public void setValue(ByteString value) {
            this.value = value;
        }

        public UnsignedInteger getGas_limit() {
            return gas_limit;
        }

        public void setGas_limit(UnsignedInteger gas_limit) {
            this.gas_limit = gas_limit;
        }

        public UnsignedInteger getMethod_num() {
            return method_num;
        }

        public void setMethod_num(UnsignedInteger method_num) {
            this.method_num = method_num;
        }

        public ByteString getParams() {
            return params;
        }

        public void setParams(ByteString params) {
            this.params = params;
        }

}

依赖的库

implementation 'org.bitcoinj:bitcoinj-core:0.15.7'
implementation 'co.nstant.in:cbor:0.9'
implementation ('org.web3j:core:4.2.0-android') {
        exclude group:'org.bouncycastle'
}