学历认证

功能描述

智能合约 实现了一个简单的征信管理的案例。针对于学历认证领域,由于条约公开,在条约外无法随意篡改的特性,天然具备稳定性和中立性。

/*
    authors:
        "swb"<swbsin@163.com>
        "Gymgle"<ymgongcn@gmail.com>
    MIT License
*/

package main

import (
    "crypto/md5"
    "crypto/rand"
    "encoding/base64"
    "encoding/hex"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "strconv"
    "time"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
)

type SimpleChaincode struct {
}

var BackGroundNo int = 0
var RecordNo int = 0

type School struct {
    Name           string
    Location       string
    Address        string
    PriKey         string
    PubKey         string
    StudentAddress []string
}

type Student struct {
    Name         string
    Address      string
    BackgroundId []int
}

// 学历信息,当离开学校才能记入
type Background struct {
    Id       int
    ExitTime int64
    Status   string //0:毕业 1:退学
}

type Record struct {
    Id              int
    SchoolAddress   string
    StudentAddress  string
    SchoolSign      string
    ModifyTime      int64
    ModifyOperation string // 0:正常毕业 1:退学 2:入学
}

/*
 * 区块链网络实例化"diploma"智能合约时调用该方法
 */
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Success(nil)
}

/*
 * 客户端发起请求执行"diploma"智能合约时会调用 Invoke 方法
 */
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    // 获取请求调用智能合约的方法和参数
    function, args := stub.GetFunctionAndParameters()
    // Route to the appropriate handler function to interact with the ledger appropriately
    if function == "createSchool" {
        return t.createSchool(stub, args)
    } else if function == "createStudent" {
        return t.createStudent(stub, args)
    } else if function == "enrollStudent" {
        return t.enrollStudent(stub, args)
    } else if function == "updateDiploma" {
        return t.updateDiploma(stub, args)
    } else if function == "getRecords" {
        return t.getRecords(stub)
    } else if function == "getRecordById" {
        return t.getRecordById(stub, args)
    } else if function == "getStudentByAddress" {
        return t.getStudentByAddress(stub, args)
    } else if function == "getSchoolByAddress" {
        return t.getSchoolByAddress(stub, args)
    } else if function == "getBackgroundById" {
        return t.getBackgroundById(stub, args)
    }
    return shim.Success(nil)
}

/*
 * 添加一所新学校
 * args[0] 学校名称
 * args[1] 学校所在位置
 */
func (t *SimpleChaincode) createSchool(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 2 {
        return shim.Error("Incorrect number of arguments. Expecting 2")
    }

    var school School
    var schoolBytes []byte
    var stuAddress []string
    var address, priKey, pubKey string
    address, priKey, pubKey = GetAddress()

    school = School{Name: args[0], Location: args[1], Address: address, PriKey: priKey, PubKey: pubKey, StudentAddress: stuAddress}
    err := writeSchool(stub, school)
    if err != nil {
        shim.Error("Error write school")
    }

    schoolBytes, err = json.Marshal(&school)
    if err != nil {
        return shim.Error("Error retrieving schoolBytes")
    }

    return shim.Success(schoolBytes)
}

/*
 * 添加一名新学生
 * args[0] 学生的姓名
 */
func (t *SimpleChaincode) createStudent(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    var student Student
    var studentBytes []byte
    var stuAddress string
    var bgID []int
    stuAddress, _, _ = GetAddress()

    student = Student{Name: args[0], Address: stuAddress, BackgroundId: bgID}
    err := writeStudent(stub, student)
    if err != nil {
        return shim.Error("Error write student")
    }

    studentBytes, err = json.Marshal(&student)
    if err != nil {
        return shim.Error("Error retrieving studentBytes")
    }

    return shim.Success(studentBytes)
}

/*
 * 学校招生(返回学校信息)
 * args[0] 学校账户地址
 * args[1] 学校签名
 * args[2] 学生账户地址
 */
func (t *SimpleChaincode) enrollStudent(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 3 {
        return shim.Error("Incorrect number of arguments. Expecting 3")
    }

    schAddress := args[0]
    schoolSign := args[1]
    stuAddress := args[2]

    var school School
    var schBytes []byte
    var err error

    // 根据学校账户地址获取学校信息
    schBytes, err = stub.GetState(schAddress)
    if err != nil {
        return shim.Error("Error retrieving data")
    }
    err = json.Unmarshal(schBytes, &school)
    if err != nil {
        return shim.Error("Error unmarshalling data")
    }

    var record Record
    record = Record{Id: RecordNo, SchoolAddress: schAddress, StudentAddress: stuAddress, SchoolSign: schoolSign, ModifyTime: time.Now().Unix(), ModifyOperation: "2"} // 2 表示入学

    err = writeRecord(stub, record)
    if err != nil {
        return shim.Error("Error write record")
    }

    school.StudentAddress = append(school.StudentAddress, stuAddress)
    err = writeSchool(stub, school)
    if err != nil {
        return shim.Error("Error write school")
    }

    RecordNo = RecordNo + 1
    recordBytes, err := json.Marshal(&record)
    if err != nil {
        return shim.Error("Error retrieving recordBytes")
    }

    return shim.Success(recordBytes)
}

/*
 * 由学校更新学生学历信息,并签名(返回记录信息)
 * args[0] 学校账户地址
 * args[1] 学校签名
 * args[2] 待修改学生的账户地址
 * args[3] 对该学生的学历进行怎样的修改,0:正常毕业  1:退学
 */
func (t *SimpleChaincode) updateDiploma(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 4 {
        return shim.Error("Incorrect number of arguments. Expecting 4")
    }

    schAddress := args[0]
    schoolSign := args[1]
    stuAddress := args[2]
    modOperation := args[3]

    var recordBytes []byte
    var student Student
    var stuBytes []byte
    var err error

    // 根据学生账户地址获取学生信息
    stuBytes, err = stub.GetState(stuAddress)
    if err != nil {
        return shim.Error("Error retrieving data")
    }
    err = json.Unmarshal(stuBytes, &student)
    if err != nil {
        return shim.Error("Error unmarshalling data")
    }

    var record Record
    record = Record{Id: RecordNo, SchoolAddress: schAddress, StudentAddress: stuAddress, SchoolSign: schoolSign, ModifyTime: time.Now().Unix(), ModifyOperation: modOperation}

    err = writeRecord(stub, record)
    if err != nil {
        return shim.Error("Error write record")
    }

    var background Background
    background = Background{Id: BackGroundNo, ExitTime: time.Now().Unix(), Status: modOperation}
    err = writeBackground(stub, background)
    if err != nil {
        return shim.Error("Error write background")
    }

    // 如果学生正常毕业,也要更新学生的教育背景
    if modOperation == "0" {
        student.BackgroundId = append(student.BackgroundId, BackGroundNo)
        student = Student{Name: student.Name, Address: student.Address, BackgroundId: student.BackgroundId}
        err = writeStudent(stub, student)
        if err != nil {
            return shim.Error("Error write student")
        }
    }

    BackGroundNo = BackGroundNo + 1
    recordBytes, err = json.Marshal(&record)
    if err != nil {
        return shim.Error("Error retrieving schoolBytes")
    }

    return shim.Success(recordBytes)
}

/*
 * 通过学生的地址获取学生的学历信息
 * args[0] address
 */
func (t *SimpleChaincode) getStudentByAddress(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    stuBytes, err := stub.GetState(args[0])
    if err != nil {
        shim.Error("Error retrieving data")
    }
    return shim.Success(stuBytes)
}

/*
 * 通过地址获取学校的信息
 * args[0] address
 */
func (t *SimpleChaincode) getSchoolByAddress(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    schBytes, err := stub.GetState(args[0])
    if err != nil {
        shim.Error("Error retrieving data")
    }
    return shim.Success(schBytes)
}

/*
 * 通过 Id 获取记录
 * args[0] 记录的 Id
 */
func (t *SimpleChaincode) getRecordById(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    recBytes, err := stub.GetState("Record" + args[0])
    if err != nil {
        return shim.Error("Error retrieving data")
    }

    return shim.Success(recBytes)
}

/*
 * 获取全部记录(如果记录数大于10,返回前10个)
 */
func (t *SimpleChaincode) getRecords(stub shim.ChaincodeStubInterface) pb.Response {
    var records []Record
    var number string
    var err error
    var record Record
    var recBytes []byte
    if RecordNo < 10 {
        i := 0
        for i <= RecordNo {
            number = strconv.Itoa(i)
            recBytes, err = stub.GetState("Record" + number)
            if err != nil {
                return shim.Error("Error get detail")
            }
            err = json.Unmarshal(recBytes, &record)
            if err != nil {
                return shim.Error("Error unmarshalling data")
            }
            records = append(records, record)
            i = i + 1
        }
    } else {
        i := 0
        for i < 10 {
            number = strconv.Itoa(i)
            recBytes, err = stub.GetState("Record" + number)
            if err != nil {
                return shim.Error("Error get detail")
            }
            err = json.Unmarshal(recBytes, &record)
            if err != nil {
                return shim.Error("Error unmarshalling data")
            }
            records = append(records, record)
            i = i + 1
        }
    }
    recordsBytes, err := json.Marshal(&records)
    if err != nil {
        shim.Error("Error get records")
    }
    return shim.Success(recordsBytes)
}

/*
 * 通过 Id 获取所存储的学历信息
 * args[0] ID
 */
func (t *SimpleChaincode) getBackgroundById(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    backBytes, err := stub.GetState("BackGround" + args[0])
    if err != nil {
        return shim.Error("Error retrieving data")
    }
    return shim.Success(backBytes)
}

func writeRecord(stub shim.ChaincodeStubInterface, record Record) error {
    var recID string
    recordBytes, err := json.Marshal(&record)
    if err != nil {
        return err
    }

    recID = strconv.Itoa(record.Id)
    err = stub.PutState("Record"+recID, recordBytes)
    if err != nil {
        return errors.New("PutState Error" + err.Error())
    }
    return nil
}

func writeSchool(stub shim.ChaincodeStubInterface, school School) error {
    schBytes, err := json.Marshal(&school)
    if err != nil {
        return err
    }

    err = stub.PutState(school.Address, schBytes)
    if err != nil {
        return errors.New("PutState Error" + err.Error())
    }
    return nil
}

func writeStudent(stub shim.ChaincodeStubInterface, student Student) error {
    stuBytes, err := json.Marshal(&student)
    if err != nil {
        return err
    }

    err = stub.PutState(student.Address, stuBytes)
    if err != nil {
        return errors.New("PutState Error" + err.Error())
    }
    return nil
}

func writeBackground(stub shim.ChaincodeStubInterface, background Background) error {
    var backID string
    backBytes, err := json.Marshal(&background)
    if err != nil {
        return err
    }

    backID = strconv.Itoa(background.Id)
    err = stub.PutState("BackGround"+backID, backBytes)
    if err != nil {
        return errors.New("PutState Error" + err.Error())
    }
    return nil
}

/*
 * 生成Address
 */
func GetAddress() (string, string, string) {
    var address, priKey, pubKey string
    b := make([]byte, 48)

    if _, err := io.ReadFull(rand.Reader, b); err != nil {
        return "", "", ""
    }

    h := md5.New()
    h.Write([]byte(base64.URLEncoding.EncodeToString(b)))

    address = hex.EncodeToString(h.Sum(nil))
    priKey = address + "1"
    pubKey = address + "2"

    return address, priKey, pubKey
}

func main() {
    err := shim.Start(new(SimpleChaincode))
    if err != nil {
        fmt.Printf("Error starting Simple chaincode: %s", err)
    }
}

该智能合约中三种角色如下:

  • 学校
  • 个人
  • 需要学历认证的机构或公司

学校可以根据相关信息在区块链上为某位个人授予学历,相关机构可以查询某人的学历信息,由于使用私钥签名,确保了信息的真实有效。
为了简单,尽量简化相关的业务,另未完成学业的学生因违纪或外出创业退学,学校可以修改其相应的学历信息。

账户私钥应该由安装在本地的客户端生成,本例中为了简便,使用模拟私钥和公钥。

数据结构设计

  • 学校
    • 名称
    • 所在位置
    • 账号地址
    • 账号公钥
    • 账户私钥
    • 学校学生
  • 个人
    • 姓名
    • 账号地址
    • 过往学历
  • 学历信息
    • 学历信息编号
    • 就读学校
    • 就读年份
    • 完成就读年份
    • 就读状态 // 0:毕业 1:退学
  • 修改记录(入学也相当于一种修改记录)
    • 编号
    • 学校账户地址(一般根据账户地址可以算出公钥地址,然后可以进行校验)
    • 学校签名
    • 个人账户地址
    • 个人公钥地址(个人不需要公钥地址)
    • 修改时间
    • 修改操作// 0:正常毕业 1:退学 2:入学

对学历操作信息所有的操作都归为记录。

function及各自实现的功能

  • init 初始化函数

  • invoke 调用合约内部的函数

  • updateDiploma 由学校更新学生学历信息,并签名(返回记录信息)

  • enrollStudent 学校招生(返回学校信息)

  • createSchool 添加一名新学校

  • createStudent 添加一名新学生

  • getStudentByAddress 通过学生的账号地址访问学生的学历信息

  • getRecordById 通过Id获取记录

  • getRecords 获取全部记录(如果记录数大于 10,返回前 10 个)

  • getSchoolByAddress 通过学校账号地址获取学校的信息

  • getBackgroundById 通过学历 Id 获取所存储的学历信息

  • writeRecord 写入记录

  • writeSchool 写入新创建的学校

  • writeStudent 写入新创建的学生

接口设计

createSchool

request参数:

args[0] 学校名称
args[1] 学校所在位置

response参数:

学校信息的字节数组,当创建一所新学校时,该学校学生账户地址列表为空

createStudent

request参数:

args[0] 学生的姓名

response参数:

学生信息的字节数组表示,刚创建过往学历信息列表为空

updateDiploma

request参数

args[0] 学校账户地址
args[1] 学校签名
args[2] 待修改学生的账户地址
args[3] //对该学生的学历进行怎样的修改,0:正常毕业  1:退学  

response参数

返回修改记录的字节数组表示

enrollStudent

request参数:

args[0] 学校账户地址
args[1] 学校签名
args[2] 学生账户地址

response参数

返回修改记录的字节数组表示

getStudentByAddress

request参数

args[0] address

response参数

学生信息的字节数组表示

getRecordById

request参数

args[0] 修改记录的ID

response参数

修改记录的字节数组表示

getRecords

response参数

获取修改记录数组(如果个数大于10,返回前10个)

getSchoolByAddress

request参数

args[0] address

response参数

学校信息的字节数组表示

getBackgroundById

request参数

args[0] ID

response参数

学历信息的字节数组表示

测试

最后编辑: kuteng  文档更新时间: 2023-05-04 16:28   作者:kuteng