DID 学习日记 - RHS 里面的 key/path 的细节
RHS 链接: https://docs.iden3.io/services/rhs/
func GenerateProof(ctx context.Context, cli NodeReader,
treeRoot *merkletree.Hash,
key *merkletree.Hash) (*merkletree.Proof, error) {
var exists bool
var siblings []*merkletree.Hash
var nodeAux *merkletree.NodeAux
mkProof := func() (*merkletree.Proof, error) {
return merkletree.NewProofFromData(exists, siblings, nodeAux)
}
nextKey := treeRoot
for depth := uint(0); depth < uint(len(key)*8); depth++ {
if *nextKey == merkletree.HashZero {
return mkProof()
}
n, err := cli.GetNode(ctx, nextKey)
if err != nil {
return nil, err
}
switch nt := n.Type(); nt {
case NodeTypeLeaf:
if bytes.Equal(key[:], n.Children[0][:]) {
exists = true
return mkProof()
}
// We found a leaf whose entry didn't match hIndex
nodeAux = &merkletree.NodeAux{
Key: n.Children[0],
Value: n.Children[1],
}
return mkProof()
case NodeTypeMiddle:
if merkletree.TestBit(key[:], depth) {
nextKey = n.Children[1]
siblings = append(siblings, n.Children[0])
} else {
nextKey = n.Children[0]
siblings = append(siblings, n.Children[1])
}
default:
return nil, fmt.Errorf(
"found unexpected node type in tree (%v): %v",
nt, n.Hash.Hex())
}
}
return nil, errors.New("tree depth is too high")
}
通过 key 的第 n 个 bit 是 0 还是 1 来决定找左右子节点
// GenerateProof generates the proof of existence (or non-existence) of an
// Entry's hash Index for a Merkle Tree given the root.
// If the rootKey is nil, the current merkletree root is used
func (mt *MerkleTree) GenerateProof(ctx context.Context, k *big.Int,
rootKey *Hash) (*Proof, *big.Int, error) {
p := &Proof{}
var siblingKey *Hash
kHash, err := NewHashFromBigInt(k)
if err != nil {
return nil, nil, err
}
path := getPath(mt.maxLevels, kHash[:])
if rootKey == nil {
rootKey = mt.Root()
}
nextKey := rootKey
for p.depth = 0; p.depth < uint(mt.maxLevels); p.depth++ {
n, err := mt.GetNode(ctx, nextKey)
if err != nil {
return nil, nil, err
}
switch n.Type {
case NodeTypeEmpty:
return p, big.NewInt(0), nil
case NodeTypeLeaf:
if bytes.Equal(kHash[:], n.Entry[0][:]) {
p.Existence = true
return p, n.Entry[1].BigInt(), nil
}
// We found a leaf whose entry didn't match hIndex
p.NodeAux = &NodeAux{Key: n.Entry[0], Value: n.Entry[1]}
return p, n.Entry[1].BigInt(), nil
case NodeTypeMiddle:
if path[p.depth] {
nextKey = n.ChildR
siblingKey = n.ChildL
} else {
nextKey = n.ChildL
siblingKey = n.ChildR
}
default:
return nil, nil, ErrInvalidNodeFound
}
if !bytes.Equal(siblingKey[:], HashZero[:]) {
SetBitBigEndian(p.notempties[:], p.depth)
p.siblings = append(p.siblings, siblingKey)
}
}
return nil, nil, ErrKeyNotFound
}
这里的 if path[p.depth] 中的 path 是哪里来的呢?
这个 k 或者 path 是自己添加的时候指定的
// Add adds a Key & Value into the MerkleTree. Where the `k` determines the
// path from the Root to the Leaf.
func (mt *MerkleTree) Add(ctx context.Context, k, v *big.Int) error {
// verify that the MerkleTree is writable
if !mt.writable {
return ErrNotWritable
}
kHash, err := NewHashFromBigInt(k)
if err != nil {
return fmt.Errorf("can't create hash from Key: %w", err)
}
vHash, err := NewHashFromBigInt(v)
if err != nil {
return fmt.Errorf("can't create hash from Value: %w", err)
}
mt.Lock()
defer mt.Unlock()
newNodeLeaf := NewNodeLeaf(kHash, vHash)
path := getPath(mt.maxLevels, kHash[:])
newRootKey, err := mt.addLeaf(ctx, newNodeLeaf, mt.rootKey, 0, path)
if err != nil {
return err
}
mt.rootKey = newRootKey
return mt.db.SetRoot(ctx, mt.rootKey)
}
那么回到 issuer 代码, 大概经过下面这几段之后
func credentialResponse(w3c *verifiable.W3CCredential, credential *domain.Claim) Credential {
var expiresAt *TimeUTC
expired := false
if w3c.Expiration != nil {
if time.Now().UTC().After(w3c.Expiration.UTC()) {
expired = true
}
expiresAt = common.ToPointer(TimeUTC(*w3c.Expiration))
}
proofs := getProofs(credential)
var refreshService *RefreshService
if w3c.RefreshService != nil {
refreshService = &RefreshService{
Id: w3c.RefreshService.ID,
Type: RefreshServiceType(w3c.RefreshService.Type),
}
}
var displayService *DisplayMethod
if w3c.DisplayMethod != nil {
displayService = &DisplayMethod{
Id: w3c.DisplayMethod.ID,
Type: DisplayMethodType(w3c.DisplayMethod.Type),
}
}
return Credential{
CredentialSubject: w3c.CredentialSubject,
CreatedAt: TimeUTC(*w3c.IssuanceDate),
Expired: expired,
ExpiresAt: expiresAt,
Id: credential.ID,
ProofTypes: proofs,
RevNonce: uint64(credential.RevNonce),
Revoked: credential.Revoked,
SchemaHash: credential.SchemaHash,
SchemaType: shortType(credential.SchemaType),
SchemaUrl: credential.SchemaURL,
UserID: credential.OtherIdentifier,
SchemaTypeDescription: credential.SchemaTypeDescription,
RefreshService: refreshService,
DisplayMethod: displayService,
}
}
/ Save creates a new claim
// 1.- Creates document
// 2.- Signature proof
// 3.- MerkelTree proof
func (c *claim) Save(ctx context.Context, req *ports.CreateClaimRequest) (*domain.Claim, error) {
claim, err := c.CreateCredential(ctx, req)
if err != nil {
return nil, err
}
claim.ID, err = c.icRepo.Save(ctx, c.storage.Pgx, claim)
if err != nil {
return nil, err
}
if req.SignatureProof {
err = c.publisher.Publish(ctx, event.CreateCredentialEvent, &event.CreateCredential{CredentialIDs: []string{claim.ID.String()}, IssuerID: req.DID.String()})
if err != nil {
log.Error(ctx, "publish CreateCredentialEvent", "err", err.Error(), "credential", claim.ID.String())
}
}
return claim, nil
}
其实对应的 RHS 的 path 或者说 key 就是这段代码
// CreateCredential - Create a new Credential, but this method doesn't save it in the repository.
func (c *claim) CreateCredential(ctx context.Context, req *ports.CreateClaimRequest) (*domain.Claim, error) {
if err := c.guardCreateClaimRequest(req); err != nil {
log.Warn(ctx, "validating create claim request", "req", req)
return nil, err
}
var nonce uint64
var err error
if req.RevNonce != nil {
nonce = *req.RevNonce
} else {
nonce, err = rand.Int64()
}
if err != nil {
log.Error(ctx, "create a nonce", "err", err)
return nil, err
}
...
...
}
注意其中这段
if req.RevNonce != nil {
nonce = *req.RevNonce
} else {
nonce, err = rand.Int64()
}
最后通过下面的 Set/Get 成为了 RHS 的 path/key
// SetRevocationNonce sets claim's revocation nonce
func (c *Claim) SetRevocationNonce(nonce uint64) {
binary.LittleEndian.PutUint64(c.value[0][:8], nonce)
}
// GetRevocationNonce returns revocation nonce
func (c *Claim) GetRevocationNonce() uint64 {
return binary.LittleEndian.Uint64(c.value[0][:8])
}
疑问
我的疑问是,如果 RevNonce 重复了, 会出现什么问题?
Update 2024-05-19
官方答复如下: https://github.com/0xPolygonID/issuer-node/issues/663