结论: 本文是区块链工程专题的原始索引,覆盖三个不同知识域,后续应拆成独立文章。
内容状态: 竞赛学习笔记,不代表生产级实现;密码学、交易签名和合约代码必须经过测试与审计。

UTXO

  • outPutTxid要写Txid前缀

  • 挖矿奖励inputTxid为nil,索引为-1.地址为nil

  • input = output,如果转账后有余额,必须转账给自己

  • 如果有多个input,要按从左往右的顺序写入

Solidity 合约

常用代码

关键字

  • pragma solidity : 开头声明版本
  • contract: 合约开头
  • constructor: 声明构造函数,注意与contract区分,太长难以记住可以分为con-struct-or
  • require: 用于确认条件是否满足,使用格式为:
    1
    2
    3
    4
    5
    function fun() public {
    require(条件语句,"字符串,可不写")
    }
    示例:
    require(a == 1,"a不等于1")
  • modifier: 函数修饰器,可以在函数执行前自动检查某个条件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    pragma solidity ^0.4.20;
    contract h {
    address owner ;
    int public a = 1;
    constructor () public {
    owner = msg.sender;///获取合约发起者的地址
    }
    modifier Own{
    require( owner == msg.sender,"");///判断当前函数调用者的地址是否等于owner
    _;//横杠一定要写,它表示了被修饰的函数的运行位置,这里在requir之后,若require失败则不运行
    }
    function fun() Own public {
    a = 10;
    }
    }

状态变量

  • msg.sender:solidity的状态变量,用于获取当前合约/函数的调用者地址,常用在构造函数中获取合约发起者的地址
  • block.timestamp(): 获取时间戳,类型为uint

合约部署

从构造函数constructor看

go技术代码

go version //查看版本

go env _w GO111MODULE=on / /开启环境变量

go env //查看库文件

go get -u golang.org/x/crypto/x/base58/… //拉取库文件

go.mod文件

运行代码和使用外部库需要使用go.mod文件,可以在vscode集成终端中输入命令创建:

1
go mod init [项目名]

时间戳

  • 时间戳转换日期:
    使用unix函数
    1
    2
    3
    4
    5
    6
       //1620221103转换
    t := time.Unix(int64(1620221103), 0)
    fmt.Println(t)
    //2021-05-05 21:25:03 +0800 CST
    //CST表示中国时间,+0800表示时差,想要转换成世界协调UTC可以:
    //2021-05-05 13:25:03 +0000 UTC
  • 计算时间差:
    使用sub函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
       //时间戳 (1666194910) 与 时间戳 (1628202212)相差总秒/总分...
    t1 := time.Unix(int64(1666194910), 0)
    t2 := time.Unix(int64(1628202212), 0)
    dT := t1.Sub(t2)
    s := dT.Seconds() //算相差总秒数
    fmt.Println(s)
    //37992698

    m := dT.Minutes() //算相差总分钟
    fmt.Println(m)
    //633211.6333333333

    h := dT.Hours() //算相差总小时
    fmt.Println(h)
    //10553.527222222223

    d := dT.Hours() / 24 //算相差总天数
    fmt.Println(d)
    //439.730300925926 取整数位

    //算日期是那年的第几天
    yearday := time.Unix(1666194910, 0).YearDay()
    fmt.Println(yearday)
    //292

    //求相差()天()时()分()秒无敌求法
    qijie := time.Unix(1666194910-1628202212, 0).UTC()
    fmt.Println(qijie) //妙算时分秒差,天数差用Day := dT.Hours()/24
    //1971-03-16 17:31:38 +0000 UTC,day = 439.730300925926
    //说明两天相差了439天,17小时,31分,38秒
    //.UTC()转换为世界协调时,因为中国有时差,转换后时间差不对

加密算法

  • md5:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
       //直接加密
    data := []byte{1} //待加密数据,如果类型不是[]byte需要强转
    hash := md5.Sum(data) //md5加密
    fmt.Println(hash)

    //先new再write加密
    md := md5.New() //new
    md.Write(data) //写入待加密数据
    hash := md.Sum(nil) //在加密后的数据前拼接nil,即不拼接
    fmt.Println(hash2)
  • RIPEMD160:
    用法类似md5的第二种用法,使用前需导入“golang.org/x/crypto/ripemd160”
    1
    2
    3
    4
    5
       data := []byte{1}
    RIP := ripemd160.New() //新建
    RIP.Write(data) //写入待加密数据
    riped := RIP.Sum(nil)
    fmt.Print(riped)
  • sha:
    sha有sha224,256等,用法类似
    1
    2
       data := []byte{1}
    sha256.Sum256(data)

编码

gob:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//编码
data := "你好" //待编码数据
buf := new(bytes.Buffer) //用来存储编码后的数据
encoder := gob.NewEncoder(buf) //新建编码器,参数为 加密后数据的存储位置
encoder.Encode([]byte(data)) //将data编码
fmt.Printf("%x\n", buf)
//090a0006e4bda0e5a5bd

//解码

deData := bytes.NewBuffer([]byte(data)) //用来存储解码后的数据
decoder := gob.NewDecoder(buf) //新建解码器,参数为待解码数据
decoder.Decode(deData) //参数为解码后数据的存储位置
fmt.Printf("%s", deData)
//你好

base64:

base64可能会与rsa加密、签名一起出现,但题目可能未提及,需要自己记住去解码

1
2
3
4
5
6
7
8
9
10
11
12
13
//将[]byte数据编码为string
data := []byte{1, 2, 3} //待编码数据
stringData := base64.StdEncoding.EncodeToString(data) //参数为待编码数据,返回编码后的字符串
fmt.Println(stringData)
//AQID

//将字符串解码回[]byte
deData, err := base64.StdEncoding.DecodeString(stringData) //参数为待解码数据,返回解码后的数据和错误err,如果不出错则err==nil
if err != nil { //出错退出,一般不会出错,可以写成 deData,_ := ....
panic(err)
}
fmt.Println(deData)
//[1 2 3]

base58:

直接encode/decode就好了

1
2
3
4
5
6
7
8
data := []byte{1, 2, 3}       //待编码数据
enData := base58.Encode(data) //编码
fmt.Println(enData)
//Ldp

deData, _ := base58.Decode(enData)//解码
fmt.Println(deData)
//[1,2,3]

文件存储

读取文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//使用os.OpenFile()
file, _ := os.OpenFile("path.txt", os.O_CREATE|os.O_RDWR, 0666) //打开path.txt文件,creat若没文件则创建,rdwr以读写模式打开,0666权限
defer file.Close() //延迟后关闭文件
info, _ := file.Stat() //获取文件属性
reader := make([]byte, info.Size()) //申请内存,用于存储文件内容
file.Read(reader) //读取文件到reader
fmt.Printf("%s", reader)
//文件内容

//使用os.Opem()打开
//Open本质是以只读模式使用OpenFile()
file, _ := os.Open("path.txt") //打开path.txt文件以读写模式打开
info, _ := file.Stat() //获取文件属性
reader := make([]byte, info.Size()) //申请内存,用于存储文件内容
file.Read(reader) //读取文件到reader
fmt.Printf("%s", reader)
//文件内容

//使用ioutil.ReadFile()读取
str, _ := ioutil.ReadFile("path.txt") //读取文件
fmt.Printf("%s", str)
//文件内容

写入文件:

1
2
3
4
5
6
7
8
9
//使用os.OpenFile()
file, _ := os.OpenFile("path.txt", os.O_CREATE|os.O_RDWR, 0666) //打开文件
defer file.Close()
data := "你好世界" //待写入数据
file.Write([]byte(data)) //将数据转换成[]byte类型后写入

//使用ioutil.WriteFile(),会直接覆盖原内容
data := []byte("世界") //待写入数据
ioutil.WriteFile("path.txt", data, 0666) //写入数据,权限666,会直接覆盖原内容

公私钥

从pem文件解析公私钥

pem目前遇见的有ecdsa和rsa两种算法,可以从密钥文件的内容直接看出,两种密钥的解析方法一样,但对于ecdsa公钥需要拼接一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//解析rsa公钥
reader, _ := ioutil.ReadFile("public.pem") //读取公钥文件
block, _ := pem.Decode(reader) //pem解码
publicInterface, _ := x509.ParsePKIXPublicKey(block.Bytes) //x509解码
publicKey := publicInterface.(*rsa.PublicKey) //类型断言,*号不要忘记加,报错说明密钥可能是其他类型
reader, _ = ioutil.ReadFile("private.pem") //读取私钥文件
block, _ = pem.Decode(reader) //pem解码
privateInterface, _ := x509.ParsePKCS8PrivateKey(block.Bytes) //注意不是CS1
privateKey := privateInterface.(*rsa.PrivateKey) //类型断言

//ecdsa解析
除了类型断言为.(*ecdsa.Public)外其他与rsa一致
//ecdsa公钥拼接
//通过x509解析出公钥publicKeyRaw后,还需要再进一步的拼接
publicKey := append(publicKeyRaw.X.Bytes(), publicKeyRaw.Y.Bytes()...) //拼接得到公钥

生成ecdsa密钥

1
2
3
4
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) //生成私钥
publicKeyRaw := privateKey.PublicKey
publicKey := append(publicKeyRaw.X.Bytes(), publicKeyRaw.Y.Bytes()...) //拼接得到公钥
fmt.Println(publicKey)

生成钱包地址

钱包地址的生成需要以下几个步骤

  1. 生成密钥对
  2. publicHash = ripemd160(sha256(publicKey))。获取公钥哈希
  3. _21byte = Version + publicHash。拼接版本号(默认写0x00)和公钥哈希
  4. checkSum = sha256(sha256(_21byte))【:4】。哈希后取前4个字节
  5. —25byte = —21byte + checkSum
  6. address = base58(_25byte)。base58编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) //生成私钥
publicKeyRaw := privateKey.PublicKey
publicKey := append(publicKeyRaw.X.Bytes(), publicKeyRaw.Y.Bytes()...) //拼接得到公钥
fmt.Println(publicKey)
sha := sha256.Sum256(publicKey)
RIP := ripemd160.New()
RIP.Write(sha[:])
publicHash := RIP.Sum(nil)
_21byte := append([]byte{0x00}, publicHash...)
hash1 := sha256.Sum256(_21byte)
hash2 := sha256.Sum256(hash1[:])
checkSum := hash2[:4]
_25byte := append(_21byte, checkSum...)
address := base58.Encode(_25byte)
fmt.Printf("%x", address)