Bytom节点机制
Bytom的主网节点是一个HD钱包,UTXO模型由HD钱包内置的账户模型管理,但是由于HD钱包的原因,导致我们无法进行
离线签名。官方的SDK关于离线签名少之又少,所以以至于有我们自己去编写sdk去完成离线签名。
源码链接
以下只贴出部分代码,不做特别申明,具体请看sdk源码。如有疑问,可添加qq:1359669022 或者微信:JFJun_12
[Java版本](https://github.com/JFJun/Bytom-offline-sign-java)
[Go版本](https://github.com/JFJun/Bytom-offline-sign-go)
生成私钥
java版本
public static String CreateXprv(){
long timestamp = System.currentTimeMillis();
String seedStr = "BytomOfflineSignTest"+timestamp;
byte[] seed;
{
try {
//创建一个64位 的随机种子
seed = new Sha512(seedStr.getBytes("utf-8")).finish512();
//创建root 私钥
byte[] privkey = RootPrivkey(seed);
String priv= Hex.encode(privkey);
return priv;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return "";
}
go版本
//生成私钥,公钥
func GenerateKey()(chainkd.XPrv, chainkd.XPub){
//生成公钥私钥
xpriv,xpub,err:=chainkd.NewXKeys(nil)
if err!=nil {
fmt.Errorf("create priv_key error,please try again,Err= %v",err)
panic(err)
}
return xpriv,xpub
}
生成地址
java版本
private String EncodingToAddress(String hrp,byte[] witnessProgram){
if (witnessProgram.length!=20){
logger.error("Input pub ripemd160 hash len is not equals 20");
return "";
}
byte[] converted = ConvertBits(witnessProgram,8,5,true);
// Concatenate the witness version and program, and encode the resulting
// bytes using bech32 encoding.
byte[] combined = new byte[converted.length+1];
combined[0] = witnessVersion;
System.arraycopy(converted,0,combined,1,converted.length);
//进行编码
String bech = Bech32Encode(hrp,combined);
//检查是否可用
byte[] program = decodeSegWitAddress(bech);
if( program==null){
logger.error("program is equals null");
return "";
}
if (!Arrays.equals(witnessProgram,program)){
logger.error("program is not equals witness program");
return "";
}
return bech;
}
go版本
//公钥转换为地址
func XpubBytesToAddress(pub []byte)string{
xpub:=BytesToXPub(pub)
address:=XpubToAddress(xpub)
return address
}
func XpubToAddress(xpub chainkd.XPub)string{
pub := xpub.PublicKey()
pubHash := crypto.Ripemd160(pub)
// TODO 切换生成主网还是测试网
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.TestNetParams) //测试网
//address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams) //主网
if err != nil {
fmt.Errorf("create address error,please try again")
panic(err)
}
return address.EncodeAddress()
}
离线签名
java版本
public static byte[] ed25519InnerSign(byte[] privateKey, byte[] message) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] digestData = new byte[32 + message.length];
int digestDataIndex = 0;
for (int i = 32; i < 64; i++) {
digestData[digestDataIndex] = privateKey[i];
digestDataIndex++;
}
for (int i = 0; i < message.length; i++) {
digestData[digestDataIndex] = message[i];
digestDataIndex++;
}
md.update(digestData);
byte[] messageDigest = md.digest();
Ed25519.reduce(messageDigest);
byte[] messageDigestReduced = Arrays.copyOfRange(messageDigest, 0, 32);
byte[] encodedR = Ed25519.scalarMultWithBaseToBytes(messageDigestReduced);
byte[] publicKey = DeriveXpub.deriveXpubByBytes(privateKey);
byte[] hramDigestData = new byte[32 + encodedR.length + message.length];
int hramDigestIndex = 0;
for (int i = 0; i < encodedR.length; i++) {
hramDigestData[hramDigestIndex] = encodedR[i];
hramDigestIndex++;
}
for (int i = 0; i < 32; i++) {
hramDigestData[hramDigestIndex] = publicKey[i];
hramDigestIndex++;
}
for (int i = 0; i < message.length; i++) {
hramDigestData[hramDigestIndex] = message[i];
hramDigestIndex++;
}
md.reset();
md.update(hramDigestData);
byte[] hramDigest = md.digest();
Ed25519.reduce(hramDigest);
byte[] hramDigestReduced = Arrays.copyOfRange(hramDigest, 0, 32);
byte[] sk = Arrays.copyOfRange(privateKey, 0, 32);
byte[] s = new byte[32];
Ed25519.mulAdd(s, hramDigestReduced, sk, messageDigestReduced);
byte[] signature = new byte[64];
for (int i = 0; i < encodedR.length; i++) {
signature[i] = encodedR[i];
}
int signatureIndex = 32;
for (int i = 0; i < s.length; i++) {
signature[signatureIndex] = s[i];
signatureIndex++;
}
return signature;
}
go版本
func SignTransaction(address string,tpl *txbuilder.Template)string{
if tpl.SigningInstructions==nil{
tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
}
var (
newTpl *txbuilder.Template
)
for i,_:=range tpl.Transaction.Inputs{
h:=tpl.Hash(uint32(i)).Byte32()
//进行签名
sig,xPub,_:=sign(address,h[:])
data:=[]byte(xPub.PublicKey())
fmt.Printf("签名数据[%d]:%s",i,hex.EncodeToString(sig))
newTpl=txbuilder.BuildSignatureDataToTplByJun(tpl,sig,data,i) //该方法写在与那么里面
}
tt,err:=txbuilder.CheckTpl(newTpl)
if err != nil {
return ""
}
fmt.Println(" Sign a transaction success !!!")
return tt.Transaction.String()
}
func sign(address string,data []byte)(sig[]byte,pub chainkd.XPub,err error){
var xPriv *chainkd.XPrv
//todo 根据地址获取对应的私钥
// xPriv:= todo
if err != nil {
return nil,chainkd.XPub{},err
}
//签名交易
sig =xPriv.Sign(data[:])
return sig,xPriv.XPub(),nil
}
发送交易
发送交易调用rpc接口“submit-transaction”。Java版本测试后可以进行多个地址向多个地址发送交易,Go版本只实现了
单个地址向一个或者多个地址发送交易。如果需要Go版本进行多对多发送交易,可自行修改。