Filcoin的交易根据地址的签名类型也分为两种, Secp256k1
和 Bls
类型。
本文讲述的是 Secp256k1
签名.
同样,如果要看懂代码实现,我们先简单看一下生成交易签名的过过程。
- 构造未签名前的交易消息结构
{
"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相关接口来进行获取。
- 获取Nonce:MpoolGetNonce
- 获取GasFeeCap:GasEstimateFeeCap
- 获取GasGasLimit:GasEstimateGasLimit
- 获取GasPremium:GasEstimateGasPremium
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'
}
全部评论